From 9a2f910ccc37b6622e0cced9df009732294651fa Mon Sep 17 00:00:00 2001 From: Jonathan Lange Date: Thu, 19 Jul 2018 17:38:01 +0100 Subject: [PATCH 001/403] Release 0.5.3 --- CHANGELOG.rst | 8 ++++++++ README.rst | 4 ++-- setup.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 914d47ce..6547f304 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog ========= +0.5.3 (2018-07-19) +================== + +Changes +------- + +* Minor markup tweaks to the README + 0.5.2 (2018-07-19) ================== diff --git a/README.rst b/README.rst index 90b4ec86..d563a8e3 100644 --- a/README.rst +++ b/README.rst @@ -160,8 +160,8 @@ Getting Help If you have any questions about, feedback for or problems with ``grafanalib``: -- Invite yourself to the `#weave-community `_ slack channel. -- Ask a question on the `#weave-community `_ slack channel. +- Invite yourself to the `Weave community `_ Slack. +- Ask a question on the `#general `_ Slack channel. - Send an email to `weave-users@weave.works `_. - `File an issue `_. diff --git a/setup.py b/setup.py index cf855f70..03f55c37 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.2', + version='0.5.3', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 80d5a68b82c2f027182f3e23134cc13fb6b371cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B5=D0=BD=D1=83=D1=81=20=D0=90=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B5=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B5?= =?UTF-8?q?=D0=B2=D0=B8=D1=87?= Date: Fri, 14 Sep 2018 18:38:29 +0300 Subject: [PATCH 002/403] Add support for custom variables --- grafanalib/core.py | 35 ++++++++++++++++++---- grafanalib/tests/test_core.py | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 23846dfe..174d02aa 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -613,6 +613,7 @@ class Template(object): name = attr.ib() query = attr.ib() + _current = attr.ib(init=False, default=attr.Factory(dict)) default = attr.ib(default=None) dataSource = attr.ib(default=None) label = attr.ib(default=None) @@ -625,6 +626,7 @@ class Template(object): default=False, validator=instance_of(bool), ) + options = attr.ib(default=attr.Factory(list)) regex = attr.ib(default=None) useTags = attr.ib( default=False, @@ -637,21 +639,42 @@ class Template(object): type = attr.ib(default='query') hide = attr.ib(default=SHOW) - def to_json_data(self): - return { - 'allValue': self.allValue, - 'current': { + def __attrs_post_init__(self): + if self.type == 'custom': + if len(self.options) == 0: + for value in self.query.split(','): + is_default = value == self.default + option = { + "selected": is_default, + "text": value, + "value": value, + } + if is_default: + self._current = option + self.options.append(option) + else: + for option in self.options: + if option['selected']: + self._current = option + break + else: + self._current = { 'text': self.default, 'value': self.default, 'tags': [], - }, + } + + def to_json_data(self): + return { + 'allValue': self.allValue, + 'current': self._current, 'datasource': self.dataSource, 'hide': self.hide, 'includeAll': self.includeAll, 'label': self.label, 'multi': self.multi, 'name': self.name, - 'options': [], + 'options': self.options, 'query': self.query, 'refresh': self.refresh, 'regex': self.regex, diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 78585e48..854d1f6e 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -3,6 +3,61 @@ import grafanalib.core as G +def test_template_defaults(): + t = G.Template( + name='test', + query='1m,5m,10m,30m,1h,3h,12h,1d', + type='interval', + default='1m', + ) + + assert t.to_json_data()['current']['text'] == '1m' + assert t.to_json_data()['current']['value'] == '1m' + + +def test_custom_template_ok(): + t = G.Template( + name='test', + query='1,2,3', + default='1', + type='custom', + ) + + assert len(t.to_json_data()['options']) == 3 + assert t.to_json_data()['current']['text'] == '1' + assert t.to_json_data()['current']['value'] == '1' + + +def test_custom_template_dont_override_options(): + t = G.Template( + name='test', + query='1,2,3', + default='1', + options=[ + { + "value": '1', + "selected": True, + "text": 'some text 1', + }, + { + "value": '2', + "selected": False, + "text": 'some text 2', + }, + { + "value": '3', + "selected": False, + "text": 'some text 3', + }, + ], + type='custom', + ) + + assert len(t.to_json_data()['options']) == 3 + assert t.to_json_data()['current']['text'] == 'some text 1' + assert t.to_json_data()['current']['value'] == '1' + + def test_table_styled_columns(): t = G.Table.with_styled_columns( columns=[ From b5769bf66a8ecccfc722062d8cea271b6860c3a2 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 7 Jan 2019 18:01:00 +0530 Subject: [PATCH 003/403] weave-users mailing list is closed: https://groups.google.com/a/weave.works/forum/#!topic/weave-users/0QXWGOPdBfY Signed-off-by: Daniel Holbach --- README.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 90b4ec86..8d9d680d 100644 --- a/README.rst +++ b/README.rst @@ -160,9 +160,8 @@ Getting Help If you have any questions about, feedback for or problems with ``grafanalib``: -- Invite yourself to the `#weave-community `_ slack channel. -- Ask a question on the `#weave-community `_ slack channel. -- Send an email to `weave-users@weave.works `_. -- `File an issue `_. +- Invite yourself to the `Weave Users Slack `_. +- Ask a question on the `#general `_ slack channel. +- `File an issue `_. Your feedback is always welcome! From 3b7b5e16855acb8b46f9a2635b1636844f461355 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 15 Apr 2019 10:37:38 +0000 Subject: [PATCH 004/403] Use raw string for regex with backslash --- grafanalib/validators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grafanalib/validators.py b/grafanalib/validators.py index 72eb47ef..16584f35 100644 --- a/grafanalib/validators.py +++ b/grafanalib/validators.py @@ -33,10 +33,10 @@ def is_interval(instance, attribute, value): A validator that raises a :exc:`ValueError` if the attribute value is not matching regular expression. """ - if not re.match("^[+-]?\d*[smhdMY]$", value): + if not re.match(r"^[+-]?\d*[smhdMY]$", value): raise ValueError( "valid interval should be a string " - "matching an expression: ^[+-]?\d*[smhdMY]$. " + r"matching an expression: ^[+-]?\d*[smhdMY]$. " "Examples: 24h 7d 1M +24h -24h") From b38cec5465e9b73d377f409e17ccdddef836173f Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 15 Apr 2019 14:34:54 +0000 Subject: [PATCH 005/403] Move to CircleCI 2.0 --- .circleci/config.yml | 38 ++++++++++++++++++++++++++++++++++++++ circle.yml | 27 --------------------------- 2 files changed, 38 insertions(+), 27 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 circle.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..d630ebd3 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,38 @@ +version: 2 + +jobs: + build: + docker: + - image: quay.io/weaveworks/build-golang:1.11.1-stretch + working_directory: /go/src/github.com/weaveworks/grafanalib + environment: + GOPATH: /go + steps: + - checkout + - run: + name: Install Docker client + command: | + curl -L -o /tmp/docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-17.03.0-ce.tgz + tar -xz -C /tmp -f /tmp/docker.tgz + mv /tmp/docker/* /usr/bin + - setup_remote_docker + - run: + name: Dependencies + command: | + pip install tox flake8 + make deps + - run: + name: make all + command: | + mkdir -p test-results/tests + make JUNIT_XML=test-results/tests/junit.xml all + - store_test_results: + path: test-results + - store_artifacts: + path: test-results + - deploy: + command: | + if [ "${CIRCLE_BRANCH}" == "master" ]; then + docker login -u "$QUAY_USER" -p "$QUAY_PASSWORD" quay.io + docker push quay.io/weaveworks/gfdatasource:$(./tools/image-tag) + fi diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 6051b017..00000000 --- a/circle.yml +++ /dev/null @@ -1,27 +0,0 @@ -machine: - services: - - docker - post: - - pyenv global 2.7.12 3.4.4 3.5.2 3.6.1 - environment: - PATH: $HOME/bin:$PATH - SRCDIR: /home/ubuntu/src/github.com/weaveworks/grafanalib - -dependencies: - override: - - pip install tox flake8 - - make deps - -test: - override: - - "mkdir -p $(dirname $SRCDIR) && cp -r $(pwd)/ $SRCDIR" - - "mkdir -p $CIRCLE_TEST_REPORTS/py.test/" - - cd $SRCDIR; make all - - mv $SRCDIR/junit-*.xml $CIRCLE_TEST_REPORTS/py.test/ - -deployment: - push: - branch: master - commands: - - docker login -e '.' -u "$QUAY_USER" -p "$QUAY_PASSWORD" quay.io - - docker push quay.io/weaveworks/gfdatasource:$(./tools/image-tag) From 8dab6f6d6177385c6d55ceb3f1318de581f0a4b9 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 15 Apr 2019 15:40:49 +0000 Subject: [PATCH 006/403] Save test results on CircleCI The JUNIT_XML setting was erroneously brought over from another repo; here we can just tell tox to put the results in a subdir and save that. --- .circleci/config.yml | 3 +-- .gitignore | 2 +- tox.ini | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d630ebd3..2984a4c3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,8 +24,7 @@ jobs: - run: name: make all command: | - mkdir -p test-results/tests - make JUNIT_XML=test-results/tests/junit.xml all + make all - store_test_results: path: test-results - store_artifacts: diff --git a/.gitignore b/.gitignore index adbed8c0..db820628 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ build/ dist/ .uptodate /.env -/junit-*.xml +test-results/junit-*.xml /.cache .ensure-* /.tox diff --git a/tox.ini b/tox.ini index ca09d4d1..02019bfb 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = py27, py34, py35, py36 [testenv] -commands = pytest --junitxml=junit-{envname}.xml +commands = pytest --junitxml=test-results/junit-{envname}.xml deps = pytest From 42941c5b063c14b07e6820fb7ea0c71c4a6c976f Mon Sep 17 00:00:00 2001 From: Piotrek Olchawa Date: Sun, 5 May 2019 21:48:31 +0200 Subject: [PATCH 007/403] Add "for" param for alerts, Grafana 6.X This parametr sets a grace period during which no notifications are set. --- CHANGELOG.rst | 8 ++++++++ grafanalib/core.py | 2 ++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 914d47ce..5f7b1b48 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog ========= +0.5.3 (2019-05-05) +================== + +Changes +------- + +* Add ``for`` parameter for alerts on Grafana 6.X + 0.5.2 (2018-07-19) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 23846dfe..c0807900 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -835,6 +835,7 @@ class Alert(object): handler = attr.ib(default=1) noDataState = attr.ib(default=STATE_NO_DATA) notifications = attr.ib(default=attr.Factory(list)) + gracePeriod = attr.ib(default='5m') def to_json_data(self): return { @@ -846,6 +847,7 @@ def to_json_data(self): "name": self.name, "noDataState": self.noDataState, "notifications": self.notifications, + "for": self.gracePeriod, } From 5287451f4b580485e7d346ddb7967eb729f4fb2c Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Fri, 31 May 2019 12:39:55 +0200 Subject: [PATCH 008/403] Set default sort order of stacked-chart tooltips to DESC - Easier to pick out the large outlier values if you have many series --- grafanalib/weave.py | 1 + 1 file changed, 1 insertion(+) diff --git a/grafanalib/weave.py b/grafanalib/weave.py index 5a01ad38..24ca755e 100644 --- a/grafanalib/weave.py +++ b/grafanalib/weave.py @@ -63,6 +63,7 @@ def stacked(graph): stack=True, fill=10, tooltip=G.Tooltip( + sort=G.SORT_DESC, valueType=G.INDIVIDUAL, ), ) From 1f24c794f955ee46504e777761ca292c86d8d115 Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Fri, 31 May 2019 13:19:24 +0200 Subject: [PATCH 009/403] Correct build-golang location to dockerhub --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2984a4c3..5f240877 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 jobs: build: docker: - - image: quay.io/weaveworks/build-golang:1.11.1-stretch + - image: weaveworks/build-golang:1.11.1-stretch working_directory: /go/src/github.com/weaveworks/grafanalib environment: GOPATH: /go From d55d8f62080b2c92fec62b2c0981824a04c85ad0 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 3 Jun 2019 16:17:59 +0000 Subject: [PATCH 010/403] Move off quay.io --- .circleci/config.yml | 6 +++--- Makefile | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2984a4c3..f0356cc5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 jobs: build: docker: - - image: quay.io/weaveworks/build-golang:1.11.1-stretch + - image: weaveworks/build-golang:1.11.1-stretch working_directory: /go/src/github.com/weaveworks/grafanalib environment: GOPATH: /go @@ -32,6 +32,6 @@ jobs: - deploy: command: | if [ "${CIRCLE_BRANCH}" == "master" ]; then - docker login -u "$QUAY_USER" -p "$QUAY_PASSWORD" quay.io - docker push quay.io/weaveworks/gfdatasource:$(./tools/image-tag) + echo "$DOCKER_REGISTRY_PASSWORD" | docker login -u "$DOCKER_REGISTRY_USER" --password-stdin + docker push weaveworks/gfdatasource:$(./tools/image-tag) fi diff --git a/Makefile b/Makefile index ad42ed5f..fdc77b09 100644 --- a/Makefile +++ b/Makefile @@ -3,13 +3,13 @@ # Boiler plate for bulding Docker containers. # All this must go at top of file I'm afraid. -IMAGE_PREFIX := quay.io/weaveworks +IMAGE_PREFIX := weaveworks IMAGE_TAG := $(shell ./tools/image-tag) GIT_REVISION := $(shell git rev-parse HEAD) UPTODATE := .uptodate # Building Docker images is now automated. The convention is every directory -# with a Dockerfile in it builds an image calls quay.io/weaveworks/. +# with a Dockerfile in it builds an image calls weaveworks/. # Dependencies (i.e. things that go in the image) still need to be explicitly # declared. %/$(UPTODATE): %/Dockerfile From 76d4c33852462ff62086928b34bbc0ebe898a87e Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 3 Jun 2019 16:26:05 +0000 Subject: [PATCH 011/403] Update docker client used in build --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f0356cc5..4069f62c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -12,7 +12,7 @@ jobs: - run: name: Install Docker client command: | - curl -L -o /tmp/docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-17.03.0-ce.tgz + curl -L -o /tmp/docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-18.09.6.tgz tar -xz -C /tmp -f /tmp/docker.tgz mv /tmp/docker/* /usr/bin - setup_remote_docker From 4ec58c1d04fa452a744b628e9879fd7083424289 Mon Sep 17 00:00:00 2001 From: Kevin Gessner Date: Tue, 18 Jun 2019 19:56:31 +0000 Subject: [PATCH 012/403] support templating variable sort options --- grafanalib/core.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 23846dfe..453fd44e 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -184,6 +184,13 @@ def to_json_data(self): SHOW = 0 HIDE_LABEL = 1 HIDE_VARIABLE = 2 +SORT_DISABLED = 0 +SORT_ALPHA_ASC = 1 +SORT_ALPHA_DESC = 2 +SORT_NUMERIC_ASC = 3 +SORT_NUMERIC_DESC = 4 +SORT_ALPHA_IGNORE_CASE_ASC = 5 +SORT_ALPHA_IGNORE_CASE_DESC = 6 @attr.s @@ -636,6 +643,7 @@ class Template(object): validator=instance_of(int)) type = attr.ib(default='query') hide = attr.ib(default=SHOW) + sort = attr.ib(default=SORT_ALPHA_ASC) def to_json_data(self): return { @@ -655,7 +663,7 @@ def to_json_data(self): 'query': self.query, 'refresh': self.refresh, 'regex': self.regex, - 'sort': 1, + 'sort': self.sort, 'type': self.type, 'useTags': self.useTags, 'tagsQuery': self.tagsQuery, From 96dff90597355e8cdae6d5a8c004074c2699d21c Mon Sep 17 00:00:00 2001 From: vicmarbev Date: Mon, 10 Jun 2019 09:08:04 +0200 Subject: [PATCH 013/403] Add 'diff', 'percent_diff' and 'count_non_null' as RTYPE --- CHANGELOG.rst | 3 ++- grafanalib/core.py | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5e7baebe..674ade71 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,12 +3,13 @@ Changelog ========= Next release -================== +======= Changes ------- * Add ``for`` parameter for alerts on Grafana 6.X +* Add 'diff', 'percent_diff' and 'count_non_null' as RTYPE 0.5.3 (2018-07-19) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 7bcff57d..aa2e878a 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -120,7 +120,7 @@ def to_json_data(self): EVAL_OUTSIDE_RANGE = "outside_range" EVAL_NO_VALUE = "no_value" -# Reducer Type avg/min/max/sum/count/last/median +# Reducer Type avg/min/max/sum/count/last/median/diff/percent_diff/count_non_null RTYPE_AVG = "avg" RTYPE_MIN = "min" RTYPE_MAX = "max" @@ -128,6 +128,9 @@ def to_json_data(self): RTYPE_COUNT = "count" RTYPE_LAST = "last" RTYPE_MEDIAN = "median" +RTYPE_DIFF = "diff" +RTYPE_PERCENT_DIFF = "percent_diff" +RTYPE_COUNT_NON_NULL = "count_non_null" # Condition Type CTYPE_QUERY = "query" From 7d1c27558d0196d81760b2a53d02c8138cddb24d Mon Sep 17 00:00:00 2001 From: vicmarbev Date: Mon, 10 Jun 2019 09:16:49 +0200 Subject: [PATCH 014/403] Modify comment to pass linting rule --- grafanalib/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index aa2e878a..b4c72646 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -120,7 +120,8 @@ def to_json_data(self): EVAL_OUTSIDE_RANGE = "outside_range" EVAL_NO_VALUE = "no_value" -# Reducer Type avg/min/max/sum/count/last/median/diff/percent_diff/count_non_null +# Reducer Type +# avg/min/max/sum/count/last/median/diff/percent_diff/count_non_null RTYPE_AVG = "avg" RTYPE_MIN = "min" RTYPE_MAX = "max" From 7db52f81a6b6335af89ff0a38c5b576dbe22ad22 Mon Sep 17 00:00:00 2001 From: vicmarbev Date: Mon, 10 Jun 2019 09:18:16 +0200 Subject: [PATCH 015/403] Modify comment to pass linting rule --- grafanalib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index b4c72646..8e5ca782 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -120,7 +120,7 @@ def to_json_data(self): EVAL_OUTSIDE_RANGE = "outside_range" EVAL_NO_VALUE = "no_value" -# Reducer Type +# Reducer Type # avg/min/max/sum/count/last/median/diff/percent_diff/count_non_null RTYPE_AVG = "avg" RTYPE_MIN = "min" From c815cf0d8fe8cf4d5bb4c5122a633e9880880e20 Mon Sep 17 00:00:00 2001 From: Simon Howe Date: Wed, 3 Jul 2019 10:09:36 +0200 Subject: [PATCH 016/403] Updates Changelog / setup.py version for v0.5.4 release --- CHANGELOG.rst | 13 ++++++++++++- setup.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 674ade71..bf895b6a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,19 @@ Next release Changes ------- -* Add ``for`` parameter for alerts on Grafana 6.X +TBA + +0.5.4 (2019-07-3) +======= + +Changes +------- + * Add 'diff', 'percent_diff' and 'count_non_null' as RTYPE +* Support for changing sort value in Template Variables. +* Sort tooltips by value in Weave/Stacked-Charts +* Add ``for`` parameter for alerts on Grafana 6.X +* Add named values for the Template.hide parameter 0.5.3 (2018-07-19) ================== diff --git a/setup.py b/setup.py index 03f55c37..4f6d46ac 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.3', + version='0.5.4', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 48e612c278cfee949d37af359d9da4f9677c5c85 Mon Sep 17 00:00:00 2001 From: butlerx Date: Thu, 18 Jul 2019 09:23:25 +0100 Subject: [PATCH 017/403] close #154 add ok for alert state --- grafanalib/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index 8e5ca782..8f87bb9b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -112,6 +112,7 @@ def to_json_data(self): STATE_NO_DATA = "no_data" STATE_ALERTING = "alerting" STATE_KEEP_LAST_STATE = "keep_state" +STATE_OK = "ok" # Evaluator EVAL_GT = "gt" From 5c69f82fde8c64c0db275db346f6b2d0ebc93945 Mon Sep 17 00:00:00 2001 From: butlerx Date: Mon, 22 Jul 2019 12:02:57 +0100 Subject: [PATCH 018/403] add cardinality metric aggregator for elastic search --- grafanalib/elasticsearch.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 9a731b1a..e304b5e5 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -43,6 +43,24 @@ def to_json_data(self): } +@attr.s +class CardinalityMetricAgg(object): + """An aggregator that provides the cardinality. value among the values. + + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-cardinality-aggregation.html + + :param field: name of elasticsearch field to provide the maximum for + """ + field = attr.ib(default="", validator=instance_of(str)) + + def to_json_data(self): + return { + 'type': 'cardinality', + 'field': self.field, + 'settings': {}, + } + + @attr.s class DateHistogramGroupBy(object): """A bucket aggregator that groups results by date. From ca31c913f7c0e268217161b1bfe05a5e776e80db Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 30 Aug 2019 10:50:22 +0000 Subject: [PATCH 019/403] Update CHANGELOG with a couple more changes --- CHANGELOG.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bf895b6a..e2878566 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,7 +10,7 @@ Changes TBA -0.5.4 (2019-07-3) +0.5.4 (2019-08-30) ======= Changes @@ -20,7 +20,11 @@ Changes * Support for changing sort value in Template Variables. * Sort tooltips by value in Weave/Stacked-Charts * Add ``for`` parameter for alerts on Grafana 6.X +* Add ``STATE_OK`` for alerts * Add named values for the Template.hide parameter +* Add cardinality metric aggregator for ElasticSearch + +Many thanks to contributors @kevingessner, @2easy, @vicmarbev, @butlerx. 0.5.3 (2018-07-19) ================== From 1d27ecfe0bdd7c47103e77eff00e8e0514c604d3 Mon Sep 17 00:00:00 2001 From: Kevin Gessner Date: Tue, 1 Oct 2019 20:24:34 +0000 Subject: [PATCH 020/403] pin to attrs 19.2 and fix deprecated arguments --- grafanalib/core.py | 4 ++-- grafanalib/zabbix.py | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 8f87bb9b..0533229b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -464,7 +464,7 @@ def _balance_panels(panels): class Row(object): # TODO: jml would like to separate the balancing behaviour from this # layer. - panels = attr.ib(default=attr.Factory(list), convert=_balance_panels) + panels = attr.ib(default=attr.Factory(list), converter=_balance_panels) collapse = attr.ib( default=False, validator=instance_of(bool), ) @@ -1008,7 +1008,7 @@ class Graph(object): # XXX: This isn't a *good* default, rather it's the default Grafana uses. yAxes = attr.ib( default=attr.Factory(YAxes), - convert=to_y_axes, + converter=to_y_axes, validator=instance_of(YAxes), ) alert = attr.ib(default=None) diff --git a/grafanalib/zabbix.py b/grafanalib/zabbix.py index 20aaef89..3fb9038c 100644 --- a/grafanalib/zabbix.py +++ b/grafanalib/zabbix.py @@ -821,7 +821,7 @@ class ZabbixTriggersPanel(object): transparent = attr.ib(default=False, validator=instance_of(bool)) triggerSeverity = attr.ib( default=ZABBIX_SEVERITY_COLORS, - convert=convertZabbixSeverityColors, + converter=convertZabbixSeverityColors, ) triggers = attr.ib( default=attr.Factory(ZabbixTrigger), diff --git a/setup.py b/setup.py index 4f6d46ac..77c76261 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ def local_file(name): 'Topic :: System :: Monitoring', ], install_requires=[ - 'attrs', + 'attrs==19.2', ], extras_require={ 'dev': [ From 497fe35a405f2a00323230e7125d091c6dd1d7e6 Mon Sep 17 00:00:00 2001 From: Matt MacGillivray Date: Wed, 9 Oct 2019 00:26:14 -0400 Subject: [PATCH 021/403] Adding InfluxDBTarget to support influxdb datasources --- grafanalib/core.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index 8f87bb9b..94d17744 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -328,6 +328,31 @@ def to_json_data(self): 'datasource': self.datasource, } +@attr.s +class InfluxDBTarget(object): + """ + Metric to show. + + :param target: Graphite way to select data + """ + + query = attr.ib(default="") + format = attr.ib(default=TIME_SERIES_TARGET_FORMAT) + alias = attr.ib(default="") + measurement = attr.ib(default="") + rawQuery = True + refId = attr.ib(default="") + + def to_json_data(self): + return { + 'query': self.query, + 'resultFormat': self.format, + 'alias': self.alias, + 'measurement': self.measurement, + 'rawQuery': self.rawQuery, + 'refId': self.refId + } + @attr.s class Tooltip(object): From ace3f90d988381eb90d52ed90e381781b0719895 Mon Sep 17 00:00:00 2001 From: Oriol Tauleria Date: Fri, 18 Oct 2019 22:18:42 +0200 Subject: [PATCH 022/403] Small refactor following project patterns --- CHANGELOG.rst | 2 ++ grafanalib/core.py | 26 -------------------------- grafanalib/influxdb.py | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 grafanalib/influxdb.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e2878566..24f24448 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ Next release Changes ------- +* Add InfluxDB data source + TBA 0.5.4 (2019-08-30) diff --git a/grafanalib/core.py b/grafanalib/core.py index 94d17744..aa2bd381 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -328,32 +328,6 @@ def to_json_data(self): 'datasource': self.datasource, } -@attr.s -class InfluxDBTarget(object): - """ - Metric to show. - - :param target: Graphite way to select data - """ - - query = attr.ib(default="") - format = attr.ib(default=TIME_SERIES_TARGET_FORMAT) - alias = attr.ib(default="") - measurement = attr.ib(default="") - rawQuery = True - refId = attr.ib(default="") - - def to_json_data(self): - return { - 'query': self.query, - 'resultFormat': self.format, - 'alias': self.alias, - 'measurement': self.measurement, - 'rawQuery': self.rawQuery, - 'refId': self.refId - } - - @attr.s class Tooltip(object): diff --git a/grafanalib/influxdb.py b/grafanalib/influxdb.py new file mode 100644 index 00000000..a65eaafd --- /dev/null +++ b/grafanalib/influxdb.py @@ -0,0 +1,39 @@ +"""Helpers to create InfluxDB-specific Grafana queries.""" + +import attr + + +@attr.s +class InfluxDBTarget(object): + """ + Generates InfluxDB target JSON structure. + + Grafana docs on using InfluxDB: + https://grafana.com/docs/features/datasources/influxdb/ + InfluxDB docs on querying or reading data: + https://v2.docs.influxdata.com/v2.0/query-data/ + + :param alias: legend alias + :param format: Bucket aggregators + :param measurement: Metric Aggregators + :param query: query + :param rawQuery: target reference id + :param refId: target reference id + """ + + alias = attr.ib(default="") + format = attr.ib(default=TIME_SERIES_TARGET_FORMAT) + measurement = attr.ib(default="") + query = attr.ib(default="") + rawQuery = attr.ib(default=True) + refId = attr.ib(default="") + + def to_json_data(self): + return { + 'query': self.query, + 'resultFormat': self.format, + 'alias': self.alias, + 'measurement': self.measurement, + 'rawQuery': self.rawQuery, + 'refId': self.refId + } From f6346e05e10ee574a7a70bc14ea52b2a7e4444af Mon Sep 17 00:00:00 2001 From: ducksecops Date: Thu, 14 Nov 2019 01:48:16 +0000 Subject: [PATCH 023/403] bump alpine to 3.10 --- gfdatasource/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gfdatasource/Dockerfile b/gfdatasource/Dockerfile index 464bc1d7..8cfa492f 100644 --- a/gfdatasource/Dockerfile +++ b/gfdatasource/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.6 +FROM alpine:3.10 RUN apk add --no-cache --upgrade python3 RUN python3 -m ensurepip && pip3 install --upgrade pip COPY requirements.txt / From 7336fcec55e4ae11e181f0ed210db0c86f0d71f6 Mon Sep 17 00:00:00 2001 From: Oriol Tauleria Date: Sun, 17 Nov 2019 17:28:12 +0100 Subject: [PATCH 024/403] Repair tests (#185) * Update deprecated convert to converter * Solve deprecated use of yAxis tuple * Change deprecated imp to importlib * Change Deprecated assoc to evolve * Change Deprecated cmp to eq and sort --- README.rst | 4 +- docs/example-elasticsearch.dashboard.py | 7 +- docs/example.dashboard.py | 144 ++++++++++++------------ grafanalib/_gen.py | 4 +- grafanalib/core.py | 8 +- grafanalib/tests/test_grafanalib.py | 8 +- grafanalib/tests/test_opentsdb.py | 4 +- grafanalib/tests/test_validators.py | 4 +- grafanalib/tests/test_zabbix.py | 4 +- grafanalib/weave.py | 18 +-- 10 files changed, 104 insertions(+), 101 deletions(-) diff --git a/README.rst b/README.rst index 8d9d680d..027daa89 100644 --- a/README.rst +++ b/README.rst @@ -57,10 +57,10 @@ percentile latency: refId='E', ), ], - yAxes=[ + yAxes=G.YAxes( YAxis(format=OPS_FORMAT), YAxis(format=SHORT_FORMAT), - ], + ), alert=Alert( name="Too many 500s on Nginx", message="More than 5 QPS of 500s on Nginx for 5 minutes", diff --git a/docs/example-elasticsearch.dashboard.py b/docs/example-elasticsearch.dashboard.py index 1615c98a..8c72cdea 100644 --- a/docs/example-elasticsearch.dashboard.py +++ b/docs/example-elasticsearch.dashboard.py @@ -38,7 +38,8 @@ dataSource="elasticsearch", targets=tgts, lines=False, - legend=Legend(alignAsTable=True, rightSide=True, total=True, current=True, max=True), + legend=Legend(alignAsTable=True, rightSide=True, + total=True, current=True, max=True), lineWidth=1, nullPointMode=NULL_AS_NULL, seriesOverrides=[ @@ -68,7 +69,7 @@ "color": "#447EBC" }, ], - yAxes=[ + yAxes=G.YAxes( YAxis( label="Count", format=SHORT_FORMAT, @@ -79,7 +80,7 @@ format=SECONDS_FORMAT, decimals=2 ), - ], + ), transparent=True, span=12, ) diff --git a/docs/example.dashboard.py b/docs/example.dashboard.py index 21421565..da25ef55 100644 --- a/docs/example.dashboard.py +++ b/docs/example.dashboard.py @@ -2,78 +2,78 @@ dashboard = Dashboard( - title="Frontend Stats", - rows=[ - Row(panels=[ - Graph( - title="Frontend QPS", - dataSource='My Prometheus', - targets=[ - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"1.."}[1m]))', - legendFormat="1xx", - refId='A', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"2.."}[1m]))', - legendFormat="2xx", - refId='B', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"3.."}[1m]))', - legendFormat="3xx", - refId='C', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"4.."}[1m]))', - legendFormat="4xx", - refId='D', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', - legendFormat="5xx", - refId='E', - ), - ], - yAxes=[ - YAxis(format=OPS_FORMAT), - YAxis(format=SHORT_FORMAT), - ], - alert=Alert( - name="Too many 500s on Nginx", - message="More than 5 QPS of 500s on Nginx for 5 minutes", - alertConditions=[ - AlertCondition( - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', - legendFormat="5xx", - refId='A', - ), - timeRange=TimeRange("5m", "now"), - evaluator=GreaterThan(5), - operator=OP_AND, - reducerType=RTYPE_SUM, + title="Frontend Stats", + rows=[ + Row(panels=[ + Graph( + title="Frontend QPS", + dataSource='My Prometheus', + targets=[ + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"1.."}[1m]))', + legendFormat="1xx", + refId='A', + ), + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"2.."}[1m]))', + legendFormat="2xx", + refId='B', + ), + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"3.."}[1m]))', + legendFormat="3xx", + refId='C', + ), + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"4.."}[1m]))', + legendFormat="4xx", + refId='D', + ), + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', + legendFormat="5xx", + refId='E', + ), + ], + yAxes=G.YAxes( + YAxis(format=OPS_FORMAT), + YAxis(format=SHORT_FORMAT), ), - ], - ) - ), - Graph( - title="Frontend latency", - dataSource='My Prometheus', - targets=[ - Target( - expr='histogram_quantile(0.5, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', - legendFormat="0.5 quantile", - refId='A', - ), - Target( - expr='histogram_quantile(0.99, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', - legendFormat="0.99 quantile", - refId='B', + alert=Alert( + name="Too many 500s on Nginx", + message="More than 5 QPS of 500s on Nginx for 5 minutes", + alertConditions=[ + AlertCondition( + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', + legendFormat="5xx", + refId='A', + ), + timeRange=TimeRange("5m", "now"), + evaluator=GreaterThan(5), + operator=OP_AND, + reducerType=RTYPE_SUM, + ), + ], + ) + ), + Graph( + title="Frontend latency", + dataSource='My Prometheus', + targets=[ + Target( + expr='histogram_quantile(0.5, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', + legendFormat="0.5 quantile", + refId='A', + ), + Target( + expr='histogram_quantile(0.99, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', + legendFormat="0.99 quantile", + refId='B', + ), + ], + yAxes=single_y_axis(format=SECONDS_FORMAT), ), - ], - yAxes=single_y_axis(format=SECONDS_FORMAT), - ), - ]), - ], + ]), + ], ).auto_panel_ids() diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index fdfff743..6873054f 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -1,7 +1,7 @@ """Generate JSON Grafana dashboards.""" import argparse -import imp +import importlib import json import os import sys @@ -21,7 +21,7 @@ def load_dashboard(path): ``dashboard``. :return: A ``Dashboard`` """ - module = imp.load_source("dashboard", path) + module = importlib.load_source("dashboard", path) marker = object() dashboard = getattr(module, 'dashboard', marker) if dashboard is marker: diff --git a/grafanalib/core.py b/grafanalib/core.py index 0533229b..4fa1e36f 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -455,7 +455,7 @@ def _balance_panels(panels): auto_span = math.ceil( (TOTAL_SPAN - allotted_spans) / (len(no_span_set) or 1)) return [ - attr.assoc(panel, span=auto_span) if panel.span is None else panel + attr.evolve(panel, span=auto_span) if panel.span is None else panel for panel in panels ] @@ -483,7 +483,7 @@ def _iter_panels(self): return iter(self.panels) def _map_panels(self, f): - return attr.assoc(self, panels=list(map(f, self.panels))) + return attr.evolve(self, panels=list(map(f, self.panels))) def to_json_data(self): showTitle = False @@ -915,7 +915,7 @@ def _iter_panels(self): yield panel def _map_panels(self, f): - return attr.assoc(self, rows=[r._map_panels(f) for r in self.rows]) + return attr.evolve(self, rows=[r._map_panels(f) for r in self.rows]) def auto_panel_ids(self): """Give unique IDs all the panels without IDs. @@ -929,7 +929,7 @@ def auto_panel_ids(self): auto_ids = (i for i in itertools.count(1) if i not in ids) def set_id(panel): - return panel if panel.id else attr.assoc(panel, id=next(auto_ids)) + return panel if panel.id else attr.evolve(panel, id=next(auto_ids)) return self._map_panels(set_id) def to_json_data(self): diff --git a/grafanalib/tests/test_grafanalib.py b/grafanalib/tests/test_grafanalib.py index 51bee964..9192a83b 100644 --- a/grafanalib/tests/test_grafanalib.py +++ b/grafanalib/tests/test_grafanalib.py @@ -25,10 +25,10 @@ def test_serialization(): ), ], id=1, - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds / second"), G.YAxis(format=G.SHORT_FORMAT), - ], + ), ) stream = StringIO() _gen.write_dashboard(graph, stream) @@ -51,10 +51,10 @@ def test_auto_id(): refId='A', ), ], - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds"), G.YAxis(format=G.SHORT_FORMAT), - ], + ), ) ]), ], diff --git a/grafanalib/tests/test_opentsdb.py b/grafanalib/tests/test_opentsdb.py index f7644a61..7a1db2a6 100644 --- a/grafanalib/tests/test_opentsdb.py +++ b/grafanalib/tests/test_opentsdb.py @@ -29,10 +29,10 @@ def test_serialization_opentsdb_target(): ]), ], id=1, - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds / second"), G.YAxis(format=G.SHORT_FORMAT), - ], + ), ) stream = StringIO() _gen.write_dashboard(graph, stream) diff --git a/grafanalib/tests/test_validators.py b/grafanalib/tests/test_validators.py index af4f4049..254a0496 100644 --- a/grafanalib/tests/test_validators.py +++ b/grafanalib/tests/test_validators.py @@ -10,7 +10,9 @@ def create_attribute(): default=None, validator=None, repr=True, - cmp=True, + cmp=None, + eq=True, + order=False, hash=True, init=True) diff --git a/grafanalib/tests/test_zabbix.py b/grafanalib/tests/test_zabbix.py index fb7ebeeb..5874bd87 100644 --- a/grafanalib/tests/test_zabbix.py +++ b/grafanalib/tests/test_zabbix.py @@ -27,10 +27,10 @@ def test_serialization_zabbix_target(): ]), ], id=1, - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds / second"), G.YAxis(format=G.SHORT_FORMAT), - ], + ), ) stream = StringIO() _gen.write_dashboard(graph, stream) diff --git a/grafanalib/weave.py b/grafanalib/weave.py index 24ca755e..bb5ec8ac 100644 --- a/grafanalib/weave.py +++ b/grafanalib/weave.py @@ -17,13 +17,13 @@ RED = "#E24D42" ALIAS_COLORS = { - "1xx": YELLOW, - "2xx": GREEN, - "3xx": BLUE, - "4xx": ORANGE, - "5xx": RED, - "success": GREEN, - "error": RED, + "1xx": YELLOW, + "2xx": GREEN, + "3xx": BLUE, + "4xx": ORANGE, + "5xx": RED, + "success": GREEN, + "error": RED, } @@ -46,10 +46,10 @@ def QPSGraph(data_source, title, expressions, **kwargs): title=title, expressions=exprs, aliasColors=ALIAS_COLORS, - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.OPS_FORMAT), G.YAxis(format=G.SHORT_FORMAT), - ], + ), **kwargs )) From eff1574e82c730f6af57c26376584bdccf982e5a Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 17 Nov 2019 18:59:32 +0000 Subject: [PATCH 025/403] Update build image to latest, including OpenSSL lib --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4069f62c..0c91bc23 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 jobs: build: docker: - - image: weaveworks/build-golang:1.11.1-stretch + - image: weaveworks/build-golang:1.13.3-stretch working_directory: /go/src/github.com/weaveworks/grafanalib environment: GOPATH: /go From d02940342d4c8c94eb1c4f0de5b9c013ec376f77 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 15 Apr 2019 15:49:57 +0000 Subject: [PATCH 026/403] Update versions of Python tested Install 'pyenv' to run the installs. --- .circleci/config.yml | 6 +++++- tox.ini | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c91bc23..bd4dd3d5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,11 @@ jobs: - run: name: Dependencies command: | - pip install tox flake8 + git clone --depth 1 -b v1.2.15 https://github.com/pyenv/pyenv.git $HOME/.pyenv + $HOME/.pyenv/bin/pyenv install 2.7.16 + $HOME/.pyenv/bin/pyenv install 3.5.8 + $HOME/.pyenv/bin/pyenv install 3.7.5 + pip3 install tox flake8 make deps - run: name: make all diff --git a/tox.ini b/tox.ini index 02019bfb..d2e2f185 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py35, py36 +envlist = py27, py35, py37 [testenv] commands = pytest --junitxml=test-results/junit-{envname}.xml From fd646d04fc4b86da85909f135b0b42f36610ae66 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 17 Nov 2019 19:37:21 +0000 Subject: [PATCH 027/403] Change pip to pip3 Since the upstream build container we are using moved from Python 2.7 to 3.5 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fdc77b09..117d7778 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ IMAGE_NAMES=$(foreach dir,$(DOCKER_IMAGE_DIRS),$(patsubst %,$(IMAGE_PREFIX)/%,$( # Python-specific stuff TOX := $(shell command -v tox 2> /dev/null) -PIP := $(shell command -v pip 2> /dev/null) +PIP := $(shell command -v pip3 2> /dev/null) FLAKE8 := $(shell command -v flake8 2> /dev/null) .ensure-tox: .ensure-pip From e192c20d074278d35b662003103150f50abae315 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 17 Nov 2019 18:13:53 +0000 Subject: [PATCH 028/403] Fix up load_source() call which doesn't exist in Python 3.5 Add two different versions depending on the Python version --- grafanalib/_gen.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index 6873054f..996efa29 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -1,7 +1,6 @@ """Generate JSON Grafana dashboards.""" import argparse -import importlib import json import os import sys @@ -21,7 +20,14 @@ def load_dashboard(path): ``dashboard``. :return: A ``Dashboard`` """ - module = importlib.load_source("dashboard", path) + if sys.version_info[0] == 3 and sys.version_info[1] >= 5: + import importlib.util + spec = importlib.util.spec_from_file_location("dashboard", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + else: + import importlib + module = importlib.load_source("dashboard", path) marker = object() dashboard = getattr(module, 'dashboard', marker) if dashboard is marker: From 65600cedf41054526384ffe59f4f8aec41406377 Mon Sep 17 00:00:00 2001 From: Kevin Gessner Date: Tue, 1 Oct 2019 20:24:34 +0000 Subject: [PATCH 029/403] pin to attrs 19.2 and fix deprecated arguments --- grafanalib/core.py | 4 ++-- grafanalib/zabbix.py | 2 +- setup.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index aa2bd381..25d0546e 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -463,7 +463,7 @@ def _balance_panels(panels): class Row(object): # TODO: jml would like to separate the balancing behaviour from this # layer. - panels = attr.ib(default=attr.Factory(list), convert=_balance_panels) + panels = attr.ib(default=attr.Factory(list), converter=_balance_panels) collapse = attr.ib( default=False, validator=instance_of(bool), ) @@ -1007,7 +1007,7 @@ class Graph(object): # XXX: This isn't a *good* default, rather it's the default Grafana uses. yAxes = attr.ib( default=attr.Factory(YAxes), - convert=to_y_axes, + converter=to_y_axes, validator=instance_of(YAxes), ) alert = attr.ib(default=None) diff --git a/grafanalib/zabbix.py b/grafanalib/zabbix.py index 20aaef89..3fb9038c 100644 --- a/grafanalib/zabbix.py +++ b/grafanalib/zabbix.py @@ -821,7 +821,7 @@ class ZabbixTriggersPanel(object): transparent = attr.ib(default=False, validator=instance_of(bool)) triggerSeverity = attr.ib( default=ZABBIX_SEVERITY_COLORS, - convert=convertZabbixSeverityColors, + converter=convertZabbixSeverityColors, ) triggers = attr.ib( default=attr.Factory(ZabbixTrigger), diff --git a/setup.py b/setup.py index 4f6d46ac..77c76261 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ def local_file(name): 'Topic :: System :: Monitoring', ], install_requires=[ - 'attrs', + 'attrs==19.2', ], extras_require={ 'dev': [ From e09b0c4cc2549cd98315a062ec1e49b84f00c228 Mon Sep 17 00:00:00 2001 From: Oriol Tauleria Date: Sun, 17 Nov 2019 17:28:12 +0100 Subject: [PATCH 030/403] Repair tests (#185) * Update deprecated convert to converter * Solve deprecated use of yAxis tuple * Change deprecated imp to importlib * Change Deprecated assoc to evolve * Change Deprecated cmp to eq and sort --- README.rst | 4 +- docs/example-elasticsearch.dashboard.py | 7 +- docs/example.dashboard.py | 144 ++++++++++++------------ grafanalib/_gen.py | 4 +- grafanalib/core.py | 8 +- grafanalib/tests/test_grafanalib.py | 8 +- grafanalib/tests/test_opentsdb.py | 4 +- grafanalib/tests/test_validators.py | 4 +- grafanalib/tests/test_zabbix.py | 4 +- grafanalib/weave.py | 18 +-- 10 files changed, 104 insertions(+), 101 deletions(-) diff --git a/README.rst b/README.rst index 8d9d680d..027daa89 100644 --- a/README.rst +++ b/README.rst @@ -57,10 +57,10 @@ percentile latency: refId='E', ), ], - yAxes=[ + yAxes=G.YAxes( YAxis(format=OPS_FORMAT), YAxis(format=SHORT_FORMAT), - ], + ), alert=Alert( name="Too many 500s on Nginx", message="More than 5 QPS of 500s on Nginx for 5 minutes", diff --git a/docs/example-elasticsearch.dashboard.py b/docs/example-elasticsearch.dashboard.py index 1615c98a..8c72cdea 100644 --- a/docs/example-elasticsearch.dashboard.py +++ b/docs/example-elasticsearch.dashboard.py @@ -38,7 +38,8 @@ dataSource="elasticsearch", targets=tgts, lines=False, - legend=Legend(alignAsTable=True, rightSide=True, total=True, current=True, max=True), + legend=Legend(alignAsTable=True, rightSide=True, + total=True, current=True, max=True), lineWidth=1, nullPointMode=NULL_AS_NULL, seriesOverrides=[ @@ -68,7 +69,7 @@ "color": "#447EBC" }, ], - yAxes=[ + yAxes=G.YAxes( YAxis( label="Count", format=SHORT_FORMAT, @@ -79,7 +80,7 @@ format=SECONDS_FORMAT, decimals=2 ), - ], + ), transparent=True, span=12, ) diff --git a/docs/example.dashboard.py b/docs/example.dashboard.py index 21421565..da25ef55 100644 --- a/docs/example.dashboard.py +++ b/docs/example.dashboard.py @@ -2,78 +2,78 @@ dashboard = Dashboard( - title="Frontend Stats", - rows=[ - Row(panels=[ - Graph( - title="Frontend QPS", - dataSource='My Prometheus', - targets=[ - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"1.."}[1m]))', - legendFormat="1xx", - refId='A', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"2.."}[1m]))', - legendFormat="2xx", - refId='B', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"3.."}[1m]))', - legendFormat="3xx", - refId='C', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"4.."}[1m]))', - legendFormat="4xx", - refId='D', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', - legendFormat="5xx", - refId='E', - ), - ], - yAxes=[ - YAxis(format=OPS_FORMAT), - YAxis(format=SHORT_FORMAT), - ], - alert=Alert( - name="Too many 500s on Nginx", - message="More than 5 QPS of 500s on Nginx for 5 minutes", - alertConditions=[ - AlertCondition( - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', - legendFormat="5xx", - refId='A', - ), - timeRange=TimeRange("5m", "now"), - evaluator=GreaterThan(5), - operator=OP_AND, - reducerType=RTYPE_SUM, + title="Frontend Stats", + rows=[ + Row(panels=[ + Graph( + title="Frontend QPS", + dataSource='My Prometheus', + targets=[ + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"1.."}[1m]))', + legendFormat="1xx", + refId='A', + ), + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"2.."}[1m]))', + legendFormat="2xx", + refId='B', + ), + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"3.."}[1m]))', + legendFormat="3xx", + refId='C', + ), + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"4.."}[1m]))', + legendFormat="4xx", + refId='D', + ), + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', + legendFormat="5xx", + refId='E', + ), + ], + yAxes=G.YAxes( + YAxis(format=OPS_FORMAT), + YAxis(format=SHORT_FORMAT), ), - ], - ) - ), - Graph( - title="Frontend latency", - dataSource='My Prometheus', - targets=[ - Target( - expr='histogram_quantile(0.5, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', - legendFormat="0.5 quantile", - refId='A', - ), - Target( - expr='histogram_quantile(0.99, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', - legendFormat="0.99 quantile", - refId='B', + alert=Alert( + name="Too many 500s on Nginx", + message="More than 5 QPS of 500s on Nginx for 5 minutes", + alertConditions=[ + AlertCondition( + Target( + expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', + legendFormat="5xx", + refId='A', + ), + timeRange=TimeRange("5m", "now"), + evaluator=GreaterThan(5), + operator=OP_AND, + reducerType=RTYPE_SUM, + ), + ], + ) + ), + Graph( + title="Frontend latency", + dataSource='My Prometheus', + targets=[ + Target( + expr='histogram_quantile(0.5, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', + legendFormat="0.5 quantile", + refId='A', + ), + Target( + expr='histogram_quantile(0.99, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', + legendFormat="0.99 quantile", + refId='B', + ), + ], + yAxes=single_y_axis(format=SECONDS_FORMAT), ), - ], - yAxes=single_y_axis(format=SECONDS_FORMAT), - ), - ]), - ], + ]), + ], ).auto_panel_ids() diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index fdfff743..6873054f 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -1,7 +1,7 @@ """Generate JSON Grafana dashboards.""" import argparse -import imp +import importlib import json import os import sys @@ -21,7 +21,7 @@ def load_dashboard(path): ``dashboard``. :return: A ``Dashboard`` """ - module = imp.load_source("dashboard", path) + module = importlib.load_source("dashboard", path) marker = object() dashboard = getattr(module, 'dashboard', marker) if dashboard is marker: diff --git a/grafanalib/core.py b/grafanalib/core.py index 25d0546e..92ca0b8e 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -454,7 +454,7 @@ def _balance_panels(panels): auto_span = math.ceil( (TOTAL_SPAN - allotted_spans) / (len(no_span_set) or 1)) return [ - attr.assoc(panel, span=auto_span) if panel.span is None else panel + attr.evolve(panel, span=auto_span) if panel.span is None else panel for panel in panels ] @@ -482,7 +482,7 @@ def _iter_panels(self): return iter(self.panels) def _map_panels(self, f): - return attr.assoc(self, panels=list(map(f, self.panels))) + return attr.evolve(self, panels=list(map(f, self.panels))) def to_json_data(self): showTitle = False @@ -914,7 +914,7 @@ def _iter_panels(self): yield panel def _map_panels(self, f): - return attr.assoc(self, rows=[r._map_panels(f) for r in self.rows]) + return attr.evolve(self, rows=[r._map_panels(f) for r in self.rows]) def auto_panel_ids(self): """Give unique IDs all the panels without IDs. @@ -928,7 +928,7 @@ def auto_panel_ids(self): auto_ids = (i for i in itertools.count(1) if i not in ids) def set_id(panel): - return panel if panel.id else attr.assoc(panel, id=next(auto_ids)) + return panel if panel.id else attr.evolve(panel, id=next(auto_ids)) return self._map_panels(set_id) def to_json_data(self): diff --git a/grafanalib/tests/test_grafanalib.py b/grafanalib/tests/test_grafanalib.py index 51bee964..9192a83b 100644 --- a/grafanalib/tests/test_grafanalib.py +++ b/grafanalib/tests/test_grafanalib.py @@ -25,10 +25,10 @@ def test_serialization(): ), ], id=1, - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds / second"), G.YAxis(format=G.SHORT_FORMAT), - ], + ), ) stream = StringIO() _gen.write_dashboard(graph, stream) @@ -51,10 +51,10 @@ def test_auto_id(): refId='A', ), ], - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds"), G.YAxis(format=G.SHORT_FORMAT), - ], + ), ) ]), ], diff --git a/grafanalib/tests/test_opentsdb.py b/grafanalib/tests/test_opentsdb.py index f7644a61..7a1db2a6 100644 --- a/grafanalib/tests/test_opentsdb.py +++ b/grafanalib/tests/test_opentsdb.py @@ -29,10 +29,10 @@ def test_serialization_opentsdb_target(): ]), ], id=1, - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds / second"), G.YAxis(format=G.SHORT_FORMAT), - ], + ), ) stream = StringIO() _gen.write_dashboard(graph, stream) diff --git a/grafanalib/tests/test_validators.py b/grafanalib/tests/test_validators.py index af4f4049..254a0496 100644 --- a/grafanalib/tests/test_validators.py +++ b/grafanalib/tests/test_validators.py @@ -10,7 +10,9 @@ def create_attribute(): default=None, validator=None, repr=True, - cmp=True, + cmp=None, + eq=True, + order=False, hash=True, init=True) diff --git a/grafanalib/tests/test_zabbix.py b/grafanalib/tests/test_zabbix.py index fb7ebeeb..5874bd87 100644 --- a/grafanalib/tests/test_zabbix.py +++ b/grafanalib/tests/test_zabbix.py @@ -27,10 +27,10 @@ def test_serialization_zabbix_target(): ]), ], id=1, - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds / second"), G.YAxis(format=G.SHORT_FORMAT), - ], + ), ) stream = StringIO() _gen.write_dashboard(graph, stream) diff --git a/grafanalib/weave.py b/grafanalib/weave.py index 24ca755e..bb5ec8ac 100644 --- a/grafanalib/weave.py +++ b/grafanalib/weave.py @@ -17,13 +17,13 @@ RED = "#E24D42" ALIAS_COLORS = { - "1xx": YELLOW, - "2xx": GREEN, - "3xx": BLUE, - "4xx": ORANGE, - "5xx": RED, - "success": GREEN, - "error": RED, + "1xx": YELLOW, + "2xx": GREEN, + "3xx": BLUE, + "4xx": ORANGE, + "5xx": RED, + "success": GREEN, + "error": RED, } @@ -46,10 +46,10 @@ def QPSGraph(data_source, title, expressions, **kwargs): title=title, expressions=exprs, aliasColors=ALIAS_COLORS, - yAxes=[ + yAxes=G.YAxes( G.YAxis(format=G.OPS_FORMAT), G.YAxis(format=G.SHORT_FORMAT), - ], + ), **kwargs )) From 4e4fc6d915e0adb5c78bcc6bb033dfdc67860bdb Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 17 Nov 2019 18:59:32 +0000 Subject: [PATCH 031/403] Update build image to latest, including OpenSSL lib --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4069f62c..0c91bc23 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2 jobs: build: docker: - - image: weaveworks/build-golang:1.11.1-stretch + - image: weaveworks/build-golang:1.13.3-stretch working_directory: /go/src/github.com/weaveworks/grafanalib environment: GOPATH: /go From 0a8573a4d4f65b57885de0dd8059baa94c0441ff Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 15 Apr 2019 15:49:57 +0000 Subject: [PATCH 032/403] Update versions of Python tested Install 'pyenv' to run the installs. --- .circleci/config.yml | 6 +++++- tox.ini | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c91bc23..bd4dd3d5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,7 +19,11 @@ jobs: - run: name: Dependencies command: | - pip install tox flake8 + git clone --depth 1 -b v1.2.15 https://github.com/pyenv/pyenv.git $HOME/.pyenv + $HOME/.pyenv/bin/pyenv install 2.7.16 + $HOME/.pyenv/bin/pyenv install 3.5.8 + $HOME/.pyenv/bin/pyenv install 3.7.5 + pip3 install tox flake8 make deps - run: name: make all diff --git a/tox.ini b/tox.ini index 02019bfb..d2e2f185 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py35, py36 +envlist = py27, py35, py37 [testenv] commands = pytest --junitxml=test-results/junit-{envname}.xml From f88490d3392f5ea13f63d88d650aa8253c39a2a5 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Sun, 17 Nov 2019 19:37:21 +0000 Subject: [PATCH 033/403] Change pip to pip3 Since the upstream build container we are using moved from Python 2.7 to 3.5 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index fdc77b09..117d7778 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ IMAGE_NAMES=$(foreach dir,$(DOCKER_IMAGE_DIRS),$(patsubst %,$(IMAGE_PREFIX)/%,$( # Python-specific stuff TOX := $(shell command -v tox 2> /dev/null) -PIP := $(shell command -v pip 2> /dev/null) +PIP := $(shell command -v pip3 2> /dev/null) FLAKE8 := $(shell command -v flake8 2> /dev/null) .ensure-tox: .ensure-pip From b5eeecfb3dec0e3fd0a123a79aadd54637c3a07a Mon Sep 17 00:00:00 2001 From: Matt MacGillivray Date: Tue, 26 Nov 2019 17:24:31 -0500 Subject: [PATCH 034/403] Fixing load_source which doesn't exist in python 3.5 - PR 192 --- grafanalib/_gen.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index 6873054f..2448c746 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -1,7 +1,6 @@ """Generate JSON Grafana dashboards.""" import argparse -import importlib import json import os import sys @@ -21,7 +20,14 @@ def load_dashboard(path): ``dashboard``. :return: A ``Dashboard`` """ - module = importlib.load_source("dashboard", path) + if sys.version_info[0] == 3 and sys.version_info[1] >= 5: + import importlib.util + spec = importlib.util.spec_from_file_location("dashboard", path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + else: + import importlib + module = importlib.load_source("dashboard", path) marker = object() dashboard = getattr(module, 'dashboard', marker) if dashboard is marker: From 0bc81052ed4471de3df0ae8364628f8ed3b9ddf5 Mon Sep 17 00:00:00 2001 From: Matt MacGillivray Date: Wed, 27 Nov 2019 01:45:26 -0500 Subject: [PATCH 035/403] fixing PR192 importlib for python 3.7 --- grafanalib/_gen.py | 1 - 1 file changed, 1 deletion(-) diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index 2448c746..1990cbe0 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -21,7 +21,6 @@ def load_dashboard(path): :return: A ``Dashboard`` """ if sys.version_info[0] == 3 and sys.version_info[1] >= 5: - import importlib.util spec = importlib.util.spec_from_file_location("dashboard", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) From 02396dbc2b14e111381fe8f7a207e37b7e4a12e1 Mon Sep 17 00:00:00 2001 From: Matt MacGillivray Date: Wed, 27 Nov 2019 01:54:15 -0500 Subject: [PATCH 036/403] Fix importlib import --- grafanalib/_gen.py | 1 + 1 file changed, 1 insertion(+) diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index 1990cbe0..2448c746 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -21,6 +21,7 @@ def load_dashboard(path): :return: A ``Dashboard`` """ if sys.version_info[0] == 3 and sys.version_info[1] >= 5: + import importlib.util spec = importlib.util.spec_from_file_location("dashboard", path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) From d715c8bb9696794f7006ba43ed900ec5e855e331 Mon Sep 17 00:00:00 2001 From: butlerx Date: Sun, 15 Sep 2019 11:47:00 +0100 Subject: [PATCH 037/403] add guage panel --- CHANGELOG.rst | 2 +- grafanalib/core.py | 157 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e2878566..dc7d4f95 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,7 @@ Next release Changes ------- -TBA +* Add ``GuagePanel`` for creating guages in grafana 6 0.5.4 (2019-08-30) ======= diff --git a/grafanalib/core.py b/grafanalib/core.py index 4fa1e36f..9d9def91 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -75,6 +75,7 @@ def to_json_data(self): TABLE_TYPE = 'table' TEXT_TYPE = 'text' ALERTLIST_TYPE = "alertlist" +GAUGE_TYPE = "gauge" DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -197,6 +198,21 @@ def to_json_data(self): SORT_ALPHA_IGNORE_CASE_ASC = 5 SORT_ALPHA_IGNORE_CASE_DESC = 6 +GAUGE_CALC_LAST = "last" +GAUGE_CALC_FIRST = "first" +GAUGE_CALC_MIN = "min" +GAUGE_CALC_MAX = "max" +GAUGE_CALC_MEAN = "mean" +GAUGE_CALC_TOTAL = "total" +GAUGE_CALC_COUNT = "count" +GAUGE_CALC_RANGE = "range" +GAUGE_CALC_DELTA = "delta" +GAUGE_CALC_STEP = "step" +GAUGE_CALC_DIFFERENCE = "difference" +GAUGE_CALC_LOGMIN = "logmin" +GAUGE_CALC_CHANGE_COUNT = "changeCount" +GAUGE_CALC_DISTINCT_COUNT = "distinctCount" + @attr.s class Mapping(object): @@ -1577,3 +1593,144 @@ def to_json_data(self): 'transparent': self.transparent, 'type': TABLE_TYPE, } + + +@attr.s +class Threshold(object): + """Threshold for a gauge + + :param color: color of threshold + :param index: index of color in gauge + :param value: when to use this color will be null if index is 0 + """ + + color = attr.ib() + index = attr.ib(validator=instance_of(int)) + value = attr.ib(validator=instance_of(float)) + + def to_json_data(self): + return { + "color": self.color, + "index": self.index, + "value": "null" if self.index == 0 else self.value, + } + + +@attr.s +class GaugePanel(object): + """Generates Gauge panel json structure + + :param allValue: If All values should be shown or a Calculation + :param cacheTimeout: metric query result cache ttl + :param calc: Calculation to perform on metrics + :param dataSource: Grafana datasource name + :param decimals: override automatic decimal precision for legend/tooltips + :param description: optional panel description + :param editable: defines if panel is editable via web interfaces + :param format: defines value units + :param height: defines panel height + :param hideTimeOverride: hides time overrides + :param id: panel id + :param interval: defines time interval between metric queries + :param labels: oprion to show gauge level labels + :param limit: limit of number of values to show when not Calculating + :param links: additional web links + :param max: maximum value of the gauge + :param maxDataPoints: maximum metric query results, + that will be used for rendering + :param min: minimum value of the gauge + :param minSpan: minimum span number + :param rangeMaps: the list of value to text mappings + :param span: defines the number of spans that will be used for panel + :param targets: list of metric requests for chosen datasource + :param thresholdLabel: label for gauge. Template Variables: + "$__series_namei" "$__field_name" "$__cell_{N} / $__calc" + :param thresholdMarkers: option to show marker of level on gauge + :param thresholds: single stat thresholds + :param timeFrom: time range that Override relative time + :param title: panel title + :param transparent: defines if panel should be transparent + :param valueMaps: the list of value to text mappings + """ + + title = attr.ib() + targets = attr.ib() + allValues = attr.ib(default=False, validator=instance_of(bool)) + cacheTimeout = attr.ib(default=None) + calc = attr.ib(default=GAUGE_CALC_MEAN) + dataSource = attr.ib(default=None) + decimals = attr.ib(default=None) + description = attr.ib(default=None) + editable = attr.ib(default=True, validator=instance_of(bool)) + format = attr.ib(default="none") + height = attr.ib(default=None) + hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) + id = attr.ib(default=None) + interval = attr.ib(default=None) + label = attr.ib(default=None) + limit = attr.ib(default=None) + links = attr.ib(default=attr.Factory(list)) + max = attr.ib(default=100) + maxDataPoints = attr.ib(default=100) + min = attr.ib(default=0) + minSpan = attr.ib(default=None) + rangeMaps = attr.ib(default=attr.Factory(list)) + repeat = attr.ib(default=None) + span = attr.ib(default=6) + thresholdLabels = attr.ib(default=False, validator=instance_of(bool)) + thresholdMarkers = attr.ib(default=True, validator=instance_of(bool)) + thresholds = attr.ib( + default=attr.Factory( + lambda: [ + Threshold("green", 0, 0), + Threshold("red", 1, 80) + ] + ), + validator=instance_of(list), + ) + timeFrom = attr.ib(default=None) + timeShift = attr.ib(default=None) + transparent = attr.ib(default=False, validator=instance_of(bool)) + valueMaps = attr.ib(default=attr.Factory(list)) + + def to_json_data(self): + return { + "cacheTimeout": self.cacheTimeout, + "datasource": self.dataSource, + "description": self.description, + "editable": self.editable, + "height": self.height, + "hideTimeOverride": self.hideTimeOverride, + "id": self.id, + "interval": self.interval, + "links": self.links, + "maxDataPoints": self.maxDataPoints, + "minSpan": self.minSpan, + "options": { + "fieldOptions": { + "calcs": [self.calc], + "defaults": { + "decimals": self.decimals, + "max": self.max, + "min": self.min, + "title": self.label, + "unit": self.format, + }, + "limit": self.limit, + "mappings": self.valueMaps, + "override": {}, + "thresholds": self.thresholds, + "values": self.allValues, + }, + "showThresholdLabels": self.thresholdLabels, + "showThresholdMarkers": self.thresholdMarkers, + }, + "repeat": self.repeat, + "span": self.span, + "targets": self.targets, + "timeFrom": self.timeFrom, + "timeShift": self.timeShift, + "title": self.title, + "transparent": self.transparent, + "type": GAUGE_TYPE, + } From aea476631d5256e731297bf0c1dfee574b7be597 Mon Sep 17 00:00:00 2001 From: butlerx Date: Sun, 15 Sep 2019 11:47:00 +0100 Subject: [PATCH 038/403] Add bar guage panel --- CHANGELOG.rst | 2 +- grafanalib/core.py | 182 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e2878566..adcb92ea 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,7 @@ Next release Changes ------- -TBA +* Add ``BarGuage`` for creating bar guages panels in grafana 6 0.5.4 (2019-08-30) ======= diff --git a/grafanalib/core.py b/grafanalib/core.py index 4fa1e36f..16aa76d1 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -75,6 +75,7 @@ def to_json_data(self): TABLE_TYPE = 'table' TEXT_TYPE = 'text' ALERTLIST_TYPE = "alertlist" +BARGAUGE_TYPE = "bargauge" DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -197,6 +198,28 @@ def to_json_data(self): SORT_ALPHA_IGNORE_CASE_ASC = 5 SORT_ALPHA_IGNORE_CASE_DESC = 6 +GAUGE_CALC_LAST = "last" +GAUGE_CALC_FIRST = "first" +GAUGE_CALC_MIN = "min" +GAUGE_CALC_MAX = "max" +GAUGE_CALC_MEAN = "mean" +GAUGE_CALC_TOTAL = "total" +GAUGE_CALC_COUNT = "count" +GAUGE_CALC_RANGE = "range" +GAUGE_CALC_DELTA = "delta" +GAUGE_CALC_STEP = "step" +GAUGE_CALC_DIFFERENCE = "difference" +GAUGE_CALC_LOGMIN = "logmin" +GAUGE_CALC_CHANGE_COUNT = "changeCount" +GAUGE_CALC_DISTINCT_COUNT = "distinctCount" + +ORIENTATION_HORIZONTAL = "horizontal" +ORIENTATION_VERTICAL = "vertical" + +GAUGE_DISPLAY_MODE_BASIC = "basic" +GAUGE_DISPLAY_MODE_LCD = "lcd" +GAUGE_DISPLAY_MODE_GRADIENT = "gradient" + @attr.s class Mapping(object): @@ -1577,3 +1600,162 @@ def to_json_data(self): 'transparent': self.transparent, 'type': TABLE_TYPE, } + + +@attr.s +class Threshold(object): + """Threshold for a gauge + + :param color: color of threshold + :param index: index of color in gauge + :param value: when to use this color will be null if index is 0 + """ + + color = attr.ib() + index = attr.ib(validator=instance_of(int)) + value = attr.ib(validator=instance_of(float)) + + def to_json_data(self): + return { + "color": self.color, + "index": self.index, + "value": "null" if self.index == 0 else self.value, + } + + +@attr.s +class BarGauge(object): + """Generates Bar Gauge panel json structure + + :param allValue: If All values should be shown or a Calculation + :param cacheTimeout: metric query result cache ttl + :param calc: Calculation to perform on metrics + :param dataSource: Grafana datasource name + :param decimals: override automatic decimal precision for legend/tooltips + :param description: optional panel description + :param displayMode: style to display bar gauge in + :param editable: defines if panel is editable via web interfaces + :param format: defines value units + :param height: defines panel height + :param hideTimeOverride: hides time overrides + :param id: panel id + :param interval: defines time interval between metric queries + :param labels: oprion to show gauge level labels + :param limit: limit of number of values to show when not Calculating + :param links: additional web links + :param max: maximum value of the gauge + :param maxDataPoints: maximum metric query results, + that will be used for rendering + :param min: minimum value of the gauge + :param minSpan: minimum span number + :param orientation: orientation of the bar gauge + :param rangeMaps: the list of value to text mappings + :param span: defines the number of spans that will be used for panel + :param targets: list of metric requests for chosen datasource + :param thresholdLabel: label for gauge. Template Variables: + "$__series_namei" "$__field_name" "$__cell_{N} / $__calc" + :param thresholdMarkers: option to show marker of level on gauge + :param thresholds: single stat thresholds + :param timeFrom: time range that Override relative time + :param title: panel title + :param transparent: defines if panel should be transparent + :param valueMaps: the list of value to text mappings + """ + + title = attr.ib() + targets = attr.ib() + allValues = attr.ib(default=False, validator=instance_of(bool)) + cacheTimeout = attr.ib(default=None) + calc = attr.ib(default=GAUGE_CALC_MEAN) + dataSource = attr.ib(default=None) + decimals = attr.ib(default=None) + description = attr.ib(default=None) + displayMode = attr.ib( + default=GAUGE_DISPLAY_MODE_LCD, + validator=in_( + [ + GAUGE_DISPLAY_MODE_LCD, + GAUGE_DISPLAY_MODE_BASIC, + GAUGE_DISPLAY_MODE_GRADIENT, + ] + ), + ) + editable = attr.ib(default=True, validator=instance_of(bool)) + format = attr.ib(default="none") + height = attr.ib(default=None) + hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) + id = attr.ib(default=None) + interval = attr.ib(default=None) + label = attr.ib(default=None) + limit = attr.ib(default=None) + links = attr.ib(default=attr.Factory(list)) + max = attr.ib(default=100) + maxDataPoints = attr.ib(default=100) + min = attr.ib(default=0) + minSpan = attr.ib(default=None) + orientation = attr.ib( + default=ORIENTATION_HORIZONTAL, + validator=in_([ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL]), + ) + rangeMaps = attr.ib(default=attr.Factory(list)) + repeat = attr.ib(default=None) + span = attr.ib(default=6) + thresholdLabels = attr.ib(default=False, validator=instance_of(bool)) + thresholdMarkers = attr.ib(default=True, validator=instance_of(bool)) + thresholds = attr.ib( + default=attr.Factory( + lambda: [ + Threshold("green", 0, 0), + Threshold("red", 1, 80) + ] + ), + validator=instance_of(list), + ) + timeFrom = attr.ib(default=None) + timeShift = attr.ib(default=None) + transparent = attr.ib(default=False, validator=instance_of(bool)) + valueMaps = attr.ib(default=attr.Factory(list)) + + def to_json_data(self): + return { + "cacheTimeout": self.cacheTimeout, + "datasource": self.dataSource, + "description": self.description, + "editable": self.editable, + "height": self.height, + "hideTimeOverride": self.hideTimeOverride, + "id": self.id, + "interval": self.interval, + "links": self.links, + "maxDataPoints": self.maxDataPoints, + "minSpan": self.minSpan, + "options": { + "displayMode": self.displayMode, + "fieldOptions": { + "calcs": [self.calc], + "defaults": { + "decimals": self.decimals, + "max": self.max, + "min": self.min, + "title": self.label, + "unit": self.format, + }, + "limit": self.limit, + "mappings": self.valueMaps, + "override": {}, + "thresholds": self.thresholds, + "values": self.allValues, + }, + "orientation": self.orientation, + "showThresholdLabels": self.thresholdLabels, + "showThresholdMarkers": self.thresholdMarkers, + }, + "repeat": self.repeat, + "span": self.span, + "targets": self.targets, + "timeFrom": self.timeFrom, + "timeShift": self.timeShift, + "title": self.title, + "transparent": self.transparent, + "type": BARGAUGE_TYPE, + } From 1df283774099b6ce431a4fb81ba519fef45e6d0e Mon Sep 17 00:00:00 2001 From: Matt MacGillivray Date: Wed, 27 Nov 2019 10:08:35 -0500 Subject: [PATCH 039/403] Add missing variable --- grafanalib/influxdb.py | 1 + 1 file changed, 1 insertion(+) diff --git a/grafanalib/influxdb.py b/grafanalib/influxdb.py index a65eaafd..cd965d8e 100644 --- a/grafanalib/influxdb.py +++ b/grafanalib/influxdb.py @@ -2,6 +2,7 @@ import attr +TIME_SERIES_TARGET_FORMAT = "time_series" @attr.s class InfluxDBTarget(object): From fe4ef3d7c71f61f431c4ad2866b7b4214a52bc48 Mon Sep 17 00:00:00 2001 From: Alessandro Trisolini Date: Tue, 14 Jan 2020 15:02:43 +0100 Subject: [PATCH 040/403] Fix example in README --- CHANGELOG.rst | 2 +- README.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e2878566..101624d9 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,7 @@ Next release Changes ------- -TBA +* Update README.rst to make the example work 0.5.4 (2019-08-30) ======= diff --git a/README.rst b/README.rst index 027daa89..850102cc 100644 --- a/README.rst +++ b/README.rst @@ -57,7 +57,7 @@ percentile latency: refId='E', ), ], - yAxes=G.YAxes( + yAxes=YAxes( YAxis(format=OPS_FORMAT), YAxis(format=SHORT_FORMAT), ), From 74f725237edd7e2d0978ec3c87e5adf76dac0b61 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Thu, 6 Feb 2020 15:32:59 +0000 Subject: [PATCH 041/403] Add Matt Richter as maintainer --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 MAINTAINERS diff --git a/MAINTAINERS b/MAINTAINERS new file mode 100644 index 00000000..3531d239 --- /dev/null +++ b/MAINTAINERS @@ -0,0 +1,2 @@ +Bryan Boreham (@bboreham) +Matt Richter (@matthewmrichter) From ec3ac5e150f6f9fbd2cf184b49aea0a45c1da034 Mon Sep 17 00:00:00 2001 From: Istvan Papp Date: Fri, 14 Feb 2020 10:58:33 +0100 Subject: [PATCH 042/403] Fix AlertList panel generation --- CHANGELOG.rst | 1 + grafanalib/core.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 101624d9..00f9eb75 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ Next release Changes ------- +* Fix AlertList panel generation * Update README.rst to make the example work 0.5.4 (2019-08-30) diff --git a/grafanalib/core.py b/grafanalib/core.py index 4fa1e36f..04d8633d 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1166,6 +1166,7 @@ class AlertList(object): onlyAlertsOnDashboard = attr.ib(default=True, validator=instance_of(bool)) show = attr.ib(default=ALERTLIST_SHOW_CURRENT) sortOrder = attr.ib(default=SORT_ASC, validator=in_([1, 2, 3])) + span = attr.ib(default=6) stateFilter = attr.ib(default=attr.Factory(list)) title = attr.ib(default="") transparent = attr.ib(default=False, validator=instance_of(bool)) @@ -1179,6 +1180,7 @@ def to_json_data(self): 'onlyAlertsOnDashboard': self.onlyAlertsOnDashboard, 'show': self.show, 'sortOrder': self.sortOrder, + 'span': self.span, 'stateFilter': self.stateFilter, 'title': self.title, 'transparent': self.transparent, From c9a305b9d215c0912430a74fd087f04ecf4a036a Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 14 Feb 2020 15:48:53 +0100 Subject: [PATCH 043/403] Create release workflow definition Following https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ in order to make releases happen automatically closes: #56 --- .github/workflows/publish-to-test-pypi.yml | 31 ++++++++++++++++++++++ CHANGELOG.rst | 1 + 2 files changed, 32 insertions(+) create mode 100644 .github/workflows/publish-to-test-pypi.yml diff --git a/.github/workflows/publish-to-test-pypi.yml b/.github/workflows/publish-to-test-pypi.yml new file mode 100644 index 00000000..b8bee53d --- /dev/null +++ b/.github/workflows/publish-to-test-pypi.yml @@ -0,0 +1,31 @@ +name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI + +on: push + +jobs: + build-n-publish: + name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@master + - name: Set up Python 3.7 + uses: actions/setup-python@v1 + with: + python-version: 3.7 + - name: Build a binary wheel and a source tarball + run: >- + pip install wheel; + rm -rf dist; + python setup.py sdist bdist_wheel + - name: Publish distribution 📦 to PyPI + if: startsWith(github.event.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.pypi_password }} + +## Test work-flow - might be useful until we confirmed this working? +# - name: Publish distribution 📦 to Test PyPI +# uses: pypa/gh-action-pypi-publish@master +# with: +# password: ${{ secrets.test_pypi_password }} +# repository_url: https://test.pypi.org/legacy/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 101624d9..dc79118d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ Next release Changes ------- +* Automate publishing to PiPy with GitHub Actions * Update README.rst to make the example work 0.5.4 (2019-08-30) From 464190ab3042b0aaef7af8512d2f8093b9373486 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 14 Feb 2020 16:29:04 +0100 Subject: [PATCH 044/403] release 0.5.5 --- CHANGELOG.rst | 14 +++++++++++++- setup.py | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dc79118d..d3dea46a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,14 +2,26 @@ Changelog ========= -Next release +0.5.5 (2020-02-17) ======= +It's been a while since the last release and we are happy to get this one into your hands. +0.5.5 is a maintenance release, most importantly it adds support for Python > 3.5. + +We are very delighted to welcome Matt Richter on board as maintainer. + Changes ------- * Automate publishing to PiPy with GitHub Actions * Update README.rst to make the example work +* Bump Dockerfile to use Alpine 3.10 as base +* Fix up ``load_source()`` call which doesn't exist in Python 3.5 +* Update versions of Python tested +* Repair tests +* pin to attrs 19.2 and fix deprecated arguments + +Many thanks to contributors @bboreham, @dholbach, @ducksecops, @kevingessner, @matthewmrichter, @uritau. 0.5.4 (2019-08-30) ======= diff --git a/setup.py b/setup.py index 77c76261..e253eb7b 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.4', + version='0.5.5', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From a0439b46de1735b551ae89ba643ff01807aec6b8 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 17 Feb 2020 13:14:18 +0100 Subject: [PATCH 045/403] Update release instructions - update release instructions with steps I used for 0.5.5 - fix typos in changelog - add changelog entry for new release - link to reopened grafanalib channel - drop references to test.pypi.org - mention 3.7 support on landing page --- ...h-to-test-pypi.yml => publish-to-pypi.yml} | 11 ++---- CHANGELOG.rst | 13 +++++-- README.rst | 4 +-- docs/releasing.rst | 35 ++++++++++++++++--- 4 files changed, 45 insertions(+), 18 deletions(-) rename .github/workflows/{publish-to-test-pypi.yml => publish-to-pypi.yml} (63%) diff --git a/.github/workflows/publish-to-test-pypi.yml b/.github/workflows/publish-to-pypi.yml similarity index 63% rename from .github/workflows/publish-to-test-pypi.yml rename to .github/workflows/publish-to-pypi.yml index b8bee53d..ccda373e 100644 --- a/.github/workflows/publish-to-test-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -1,10 +1,10 @@ -name: Publish Python 🐍 distributions 📦 to PyPI and TestPyPI +name: Publish Python 🐍 distributions 📦 to PyPI on: push jobs: build-n-publish: - name: Build and publish Python 🐍 distributions 📦 to PyPI and TestPyPI + name: Build and publish Python 🐍 distributions 📦 to PyPI runs-on: ubuntu-18.04 steps: - uses: actions/checkout@master @@ -22,10 +22,3 @@ jobs: uses: pypa/gh-action-pypi-publish@master with: password: ${{ secrets.pypi_password }} - -## Test work-flow - might be useful until we confirmed this working? -# - name: Publish distribution 📦 to Test PyPI -# uses: pypa/gh-action-pypi-publish@master -# with: -# password: ${{ secrets.test_pypi_password }} -# repository_url: https://test.pypi.org/legacy/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d3dea46a..09382f12 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,18 +2,27 @@ Changelog ========= +Next release +======= + +Changes +------- + +* TBA + + 0.5.5 (2020-02-17) ======= It's been a while since the last release and we are happy to get this one into your hands. -0.5.5 is a maintenance release, most importantly it adds support for Python > 3.5. +0.5.5 is a maintenance release, most importantly it adds support for Python >= 3.5. We are very delighted to welcome Matt Richter on board as maintainer. Changes ------- -* Automate publishing to PiPy with GitHub Actions +* Automate publishing to PyPI with GitHub Actions * Update README.rst to make the example work * Bump Dockerfile to use Alpine 3.10 as base * Fix up ``load_source()`` call which doesn't exist in Python 3.5 diff --git a/README.rst b/README.rst index 850102cc..67fe91b6 100644 --- a/README.rst +++ b/README.rst @@ -129,7 +129,7 @@ Support This library is in its very early stages. We'll probably make changes that break backwards compatibility, although we'll try hard not to. -grafanalib works with Python 2.7, 3.4, 3.5, and 3.6. +grafanalib works with Python 2.7, 3.4, 3.5, 3.6 and 3.7. Developing ========== @@ -161,7 +161,7 @@ Getting Help If you have any questions about, feedback for or problems with ``grafanalib``: - Invite yourself to the `Weave Users Slack `_. -- Ask a question on the `#general `_ slack channel. +- Ask a question on the `#grafanalib `_ slack channel. - `File an issue `_. Your feedback is always welcome! diff --git a/docs/releasing.rst b/docs/releasing.rst index 6298f168..2103096f 100644 --- a/docs/releasing.rst +++ b/docs/releasing.rst @@ -2,14 +2,39 @@ Release process =============== +Pre-release +----------- + * Pick a new version number (e.g. ``X.Y.Z``) * Update `CHANGELOG <../CHANGELOG.rst>`_ with that number * Update `setup.py <../setup.py>`_ with that number -* Tag the repo with ``vX.Y.Z`` -* Upload to PyPI: + +Smoke-testing +------------- + +* Run + + .. code-block:: console + + $ python setup.py install + +* Check ``~/.local/bin/generate-dashboard`` for the update version. +* Try the example on `README <../README.rst>`_. + +Releasing +--------- + +* Head to ``_ and create the release there. +* Wait for GitHub Actions to complete the build and release. +* Confirm on ``_ that the release made it there. + +Follow-up +--------- + +* Run .. code-block:: console - $ rm -rf dist - $ python setup.py sdist bdist_wheel - $ twine upload dist/* + $ pip intall grafanalib -U + +* Check if the upgrade worked and the test above still passes. From 2c26833d5bd709ea4fb89351b9247813bf048ce1 Mon Sep 17 00:00:00 2001 From: Matt Richter <4481324+matthewmrichter@users.noreply.github.com> Date: Wed, 19 Feb 2020 14:49:54 -0500 Subject: [PATCH 046/403] Revert "Add bar guage panel" --- CHANGELOG.rst | 1 - grafanalib/core.py | 164 +-------------------------------------------- 2 files changed, 1 insertion(+), 164 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 60366e05..48293c3c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,6 @@ Next release Changes ------- -* Add ``BarGuage`` for creating bar guages panels in grafana 6 * Add ``GuagePanel`` for creating guages in grafana 6 * TBA diff --git a/grafanalib/core.py b/grafanalib/core.py index 6e6c9eb8..9d9def91 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -75,7 +75,6 @@ def to_json_data(self): TABLE_TYPE = 'table' TEXT_TYPE = 'text' ALERTLIST_TYPE = "alertlist" -BARGAUGE_TYPE = "bargauge" GAUGE_TYPE = "gauge" DEFAULT_FILL = 1 @@ -214,12 +213,6 @@ def to_json_data(self): GAUGE_CALC_CHANGE_COUNT = "changeCount" GAUGE_CALC_DISTINCT_COUNT = "distinctCount" -ORIENTATION_HORIZONTAL = "horizontal" -ORIENTATION_VERTICAL = "vertical" - -GAUGE_DISPLAY_MODE_BASIC = "basic" -GAUGE_DISPLAY_MODE_LCD = "lcd" -GAUGE_DISPLAY_MODE_GRADIENT = "gradient" @attr.s class Mapping(object): @@ -1622,166 +1615,11 @@ def to_json_data(self): "value": "null" if self.index == 0 else self.value, } -@attr.s -class Threshold(object): - """Threshold for a gauge - :param color: color of threshold - :param index: index of color in gauge - :param value: when to use this color will be null if index is 0 - """ - - color = attr.ib() - index = attr.ib(validator=instance_of(int)) - value = attr.ib(validator=instance_of(float)) - - def to_json_data(self): - return { - "color": self.color, - "index": self.index, - "value": "null" if self.index == 0 else self.value, - } - - -@attr.s -class BarGauge(object): - """Generates Bar Gauge panel json structure - :param allValue: If All values should be shown or a Calculation - :param cacheTimeout: metric query result cache ttl - :param calc: Calculation to perform on metrics - :param dataSource: Grafana datasource name - :param decimals: override automatic decimal precision for legend/tooltips - :param description: optional panel description - :param displayMode: style to display bar gauge in - :param editable: defines if panel is editable via web interfaces - :param format: defines value units - :param height: defines panel height - :param hideTimeOverride: hides time overrides - :param id: panel id - :param interval: defines time interval between metric queries - :param labels: oprion to show gauge level labels - :param limit: limit of number of values to show when not Calculating - :param links: additional web links - :param max: maximum value of the gauge - :param maxDataPoints: maximum metric query results, - that will be used for rendering - :param min: minimum value of the gauge - :param minSpan: minimum span number - :param orientation: orientation of the bar gauge - :param rangeMaps: the list of value to text mappings - :param span: defines the number of spans that will be used for panel - :param targets: list of metric requests for chosen datasource - :param thresholdLabel: label for gauge. Template Variables: - "$__series_namei" "$__field_name" "$__cell_{N} / $__calc" - :param thresholdMarkers: option to show marker of level on gauge - :param thresholds: single stat thresholds - :param timeFrom: time range that Override relative time - :param title: panel title - :param transparent: defines if panel should be transparent - :param valueMaps: the list of value to text mappings - """ - - title = attr.ib() - targets = attr.ib() - allValues = attr.ib(default=False, validator=instance_of(bool)) - cacheTimeout = attr.ib(default=None) - calc = attr.ib(default=GAUGE_CALC_MEAN) - dataSource = attr.ib(default=None) - decimals = attr.ib(default=None) - description = attr.ib(default=None) - displayMode = attr.ib( - default=GAUGE_DISPLAY_MODE_LCD, - validator=in_( - [ - GAUGE_DISPLAY_MODE_LCD, - GAUGE_DISPLAY_MODE_BASIC, - GAUGE_DISPLAY_MODE_GRADIENT, - ] - ), - ) - editable = attr.ib(default=True, validator=instance_of(bool)) - format = attr.ib(default="none") - height = attr.ib(default=None) - hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) - id = attr.ib(default=None) - interval = attr.ib(default=None) - label = attr.ib(default=None) - limit = attr.ib(default=None) - links = attr.ib(default=attr.Factory(list)) - max = attr.ib(default=100) - maxDataPoints = attr.ib(default=100) - min = attr.ib(default=0) - minSpan = attr.ib(default=None) - orientation = attr.ib( - default=ORIENTATION_HORIZONTAL, - validator=in_([ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL]), - ) - rangeMaps = attr.ib(default=attr.Factory(list)) - repeat = attr.ib(default=None) - span = attr.ib(default=6) - thresholdLabels = attr.ib(default=False, validator=instance_of(bool)) - thresholdMarkers = attr.ib(default=True, validator=instance_of(bool)) - thresholds = attr.ib( - default=attr.Factory( - lambda: [ - Threshold("green", 0, 0), - Threshold("red", 1, 80) - ] - ), - validator=instance_of(list), - ) - timeFrom = attr.ib(default=None) - timeShift = attr.ib(default=None) - transparent = attr.ib(default=False, validator=instance_of(bool)) - valueMaps = attr.ib(default=attr.Factory(list)) - - def to_json_data(self): - return { - "cacheTimeout": self.cacheTimeout, - "datasource": self.dataSource, - "description": self.description, - "editable": self.editable, - "height": self.height, - "hideTimeOverride": self.hideTimeOverride, - "id": self.id, - "interval": self.interval, - "links": self.links, - "maxDataPoints": self.maxDataPoints, - "minSpan": self.minSpan, - "options": { - "displayMode": self.displayMode, - "fieldOptions": { - "calcs": [self.calc], - "defaults": { - "decimals": self.decimals, - "max": self.max, - "min": self.min, - "title": self.label, - "unit": self.format, - }, - "limit": self.limit, - "mappings": self.valueMaps, - "override": {}, - "thresholds": self.thresholds, - "values": self.allValues, - }, - "orientation": self.orientation, - "showThresholdLabels": self.thresholdLabels, - "showThresholdMarkers": self.thresholdMarkers, - }, - "repeat": self.repeat, - "span": self.span, - "targets": self.targets, - "timeFrom": self.timeFrom, - "timeShift": self.timeShift, - "title": self.title, - "transparent": self.transparent, - "type": BARGAUGE_TYPE, - } - @attr.s class GaugePanel(object): """Generates Gauge panel json structure + :param allValue: If All values should be shown or a Calculation :param cacheTimeout: metric query result cache ttl :param calc: Calculation to perform on metrics From cb7d7af6a1f4aad64736501449ca3f871dee050d Mon Sep 17 00:00:00 2001 From: Matt Richter <4481324+matthewmrichter@users.noreply.github.com> Date: Wed, 19 Feb 2020 15:08:13 -0500 Subject: [PATCH 047/403] Revert "Revert "Add bar guage panel"" --- CHANGELOG.rst | 1 + grafanalib/core.py | 164 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 48293c3c..60366e05 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ Next release Changes ------- +* Add ``BarGuage`` for creating bar guages panels in grafana 6 * Add ``GuagePanel`` for creating guages in grafana 6 * TBA diff --git a/grafanalib/core.py b/grafanalib/core.py index 9d9def91..6e6c9eb8 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -75,6 +75,7 @@ def to_json_data(self): TABLE_TYPE = 'table' TEXT_TYPE = 'text' ALERTLIST_TYPE = "alertlist" +BARGAUGE_TYPE = "bargauge" GAUGE_TYPE = "gauge" DEFAULT_FILL = 1 @@ -213,6 +214,12 @@ def to_json_data(self): GAUGE_CALC_CHANGE_COUNT = "changeCount" GAUGE_CALC_DISTINCT_COUNT = "distinctCount" +ORIENTATION_HORIZONTAL = "horizontal" +ORIENTATION_VERTICAL = "vertical" + +GAUGE_DISPLAY_MODE_BASIC = "basic" +GAUGE_DISPLAY_MODE_LCD = "lcd" +GAUGE_DISPLAY_MODE_GRADIENT = "gradient" @attr.s class Mapping(object): @@ -1615,11 +1622,166 @@ def to_json_data(self): "value": "null" if self.index == 0 else self.value, } +@attr.s +class Threshold(object): + """Threshold for a gauge + :param color: color of threshold + :param index: index of color in gauge + :param value: when to use this color will be null if index is 0 + """ + + color = attr.ib() + index = attr.ib(validator=instance_of(int)) + value = attr.ib(validator=instance_of(float)) + + def to_json_data(self): + return { + "color": self.color, + "index": self.index, + "value": "null" if self.index == 0 else self.value, + } + + +@attr.s +class BarGauge(object): + """Generates Bar Gauge panel json structure + :param allValue: If All values should be shown or a Calculation + :param cacheTimeout: metric query result cache ttl + :param calc: Calculation to perform on metrics + :param dataSource: Grafana datasource name + :param decimals: override automatic decimal precision for legend/tooltips + :param description: optional panel description + :param displayMode: style to display bar gauge in + :param editable: defines if panel is editable via web interfaces + :param format: defines value units + :param height: defines panel height + :param hideTimeOverride: hides time overrides + :param id: panel id + :param interval: defines time interval between metric queries + :param labels: oprion to show gauge level labels + :param limit: limit of number of values to show when not Calculating + :param links: additional web links + :param max: maximum value of the gauge + :param maxDataPoints: maximum metric query results, + that will be used for rendering + :param min: minimum value of the gauge + :param minSpan: minimum span number + :param orientation: orientation of the bar gauge + :param rangeMaps: the list of value to text mappings + :param span: defines the number of spans that will be used for panel + :param targets: list of metric requests for chosen datasource + :param thresholdLabel: label for gauge. Template Variables: + "$__series_namei" "$__field_name" "$__cell_{N} / $__calc" + :param thresholdMarkers: option to show marker of level on gauge + :param thresholds: single stat thresholds + :param timeFrom: time range that Override relative time + :param title: panel title + :param transparent: defines if panel should be transparent + :param valueMaps: the list of value to text mappings + """ + + title = attr.ib() + targets = attr.ib() + allValues = attr.ib(default=False, validator=instance_of(bool)) + cacheTimeout = attr.ib(default=None) + calc = attr.ib(default=GAUGE_CALC_MEAN) + dataSource = attr.ib(default=None) + decimals = attr.ib(default=None) + description = attr.ib(default=None) + displayMode = attr.ib( + default=GAUGE_DISPLAY_MODE_LCD, + validator=in_( + [ + GAUGE_DISPLAY_MODE_LCD, + GAUGE_DISPLAY_MODE_BASIC, + GAUGE_DISPLAY_MODE_GRADIENT, + ] + ), + ) + editable = attr.ib(default=True, validator=instance_of(bool)) + format = attr.ib(default="none") + height = attr.ib(default=None) + hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) + id = attr.ib(default=None) + interval = attr.ib(default=None) + label = attr.ib(default=None) + limit = attr.ib(default=None) + links = attr.ib(default=attr.Factory(list)) + max = attr.ib(default=100) + maxDataPoints = attr.ib(default=100) + min = attr.ib(default=0) + minSpan = attr.ib(default=None) + orientation = attr.ib( + default=ORIENTATION_HORIZONTAL, + validator=in_([ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL]), + ) + rangeMaps = attr.ib(default=attr.Factory(list)) + repeat = attr.ib(default=None) + span = attr.ib(default=6) + thresholdLabels = attr.ib(default=False, validator=instance_of(bool)) + thresholdMarkers = attr.ib(default=True, validator=instance_of(bool)) + thresholds = attr.ib( + default=attr.Factory( + lambda: [ + Threshold("green", 0, 0), + Threshold("red", 1, 80) + ] + ), + validator=instance_of(list), + ) + timeFrom = attr.ib(default=None) + timeShift = attr.ib(default=None) + transparent = attr.ib(default=False, validator=instance_of(bool)) + valueMaps = attr.ib(default=attr.Factory(list)) + + def to_json_data(self): + return { + "cacheTimeout": self.cacheTimeout, + "datasource": self.dataSource, + "description": self.description, + "editable": self.editable, + "height": self.height, + "hideTimeOverride": self.hideTimeOverride, + "id": self.id, + "interval": self.interval, + "links": self.links, + "maxDataPoints": self.maxDataPoints, + "minSpan": self.minSpan, + "options": { + "displayMode": self.displayMode, + "fieldOptions": { + "calcs": [self.calc], + "defaults": { + "decimals": self.decimals, + "max": self.max, + "min": self.min, + "title": self.label, + "unit": self.format, + }, + "limit": self.limit, + "mappings": self.valueMaps, + "override": {}, + "thresholds": self.thresholds, + "values": self.allValues, + }, + "orientation": self.orientation, + "showThresholdLabels": self.thresholdLabels, + "showThresholdMarkers": self.thresholdMarkers, + }, + "repeat": self.repeat, + "span": self.span, + "targets": self.targets, + "timeFrom": self.timeFrom, + "timeShift": self.timeShift, + "title": self.title, + "transparent": self.transparent, + "type": BARGAUGE_TYPE, + } + @attr.s class GaugePanel(object): """Generates Gauge panel json structure - :param allValue: If All values should be shown or a Calculation :param cacheTimeout: metric query result cache ttl :param calc: Calculation to perform on metrics From dd93447324a9f24bda6c8f6c5251ccf6b57328b8 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Wed, 19 Feb 2020 15:11:06 -0500 Subject: [PATCH 048/403] Fix the gauge-bar-panel merge - remove duplicate Threshold class and add a blank line to satisfy the linter --- grafanalib/core.py | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 6e6c9eb8..5d041c1c 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -221,6 +221,7 @@ def to_json_data(self): GAUGE_DISPLAY_MODE_LCD = "lcd" GAUGE_DISPLAY_MODE_GRADIENT = "gradient" + @attr.s class Mapping(object): @@ -1622,25 +1623,6 @@ def to_json_data(self): "value": "null" if self.index == 0 else self.value, } -@attr.s -class Threshold(object): - """Threshold for a gauge - :param color: color of threshold - :param index: index of color in gauge - :param value: when to use this color will be null if index is 0 - """ - - color = attr.ib() - index = attr.ib(validator=instance_of(int)) - value = attr.ib(validator=instance_of(float)) - - def to_json_data(self): - return { - "color": self.color, - "index": self.index, - "value": "null" if self.index == 0 else self.value, - } - @attr.s class BarGauge(object): From ed29b6cb5d93e51594bb63213e76883bfab29e88 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 18 Nov 2019 00:47:58 +0000 Subject: [PATCH 049/403] Remove gfdatasource from the repo Since we don't need any tools except Python now, use the Circle CI Python image instead of a Go image. --- .circleci/config.yml | 20 +-- CHANGELOG.rst | 1 + Makefile | 4 +- README.rst | 17 +-- gfdatasource/Dockerfile | 15 -- gfdatasource/gfdatasource | 260 ---------------------------------- gfdatasource/requirements.txt | 2 - 7 files changed, 9 insertions(+), 310 deletions(-) delete mode 100644 gfdatasource/Dockerfile delete mode 100755 gfdatasource/gfdatasource delete mode 100644 gfdatasource/requirements.txt diff --git a/.circleci/config.yml b/.circleci/config.yml index bd4dd3d5..2da8db32 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,19 +3,9 @@ version: 2 jobs: build: docker: - - image: weaveworks/build-golang:1.13.3-stretch - working_directory: /go/src/github.com/weaveworks/grafanalib - environment: - GOPATH: /go + - image: circleci/python:3.7.5 steps: - checkout - - run: - name: Install Docker client - command: | - curl -L -o /tmp/docker.tgz https://download.docker.com/linux/static/stable/x86_64/docker-18.09.6.tgz - tar -xz -C /tmp -f /tmp/docker.tgz - mv /tmp/docker/* /usr/bin - - setup_remote_docker - run: name: Dependencies command: | @@ -23,7 +13,7 @@ jobs: $HOME/.pyenv/bin/pyenv install 2.7.16 $HOME/.pyenv/bin/pyenv install 3.5.8 $HOME/.pyenv/bin/pyenv install 3.7.5 - pip3 install tox flake8 + pip3 install --user tox flake8 make deps - run: name: make all @@ -33,9 +23,3 @@ jobs: path: test-results - store_artifacts: path: test-results - - deploy: - command: | - if [ "${CIRCLE_BRANCH}" == "master" ]; then - echo "$DOCKER_REGISTRY_PASSWORD" | docker login -u "$DOCKER_REGISTRY_USER" --password-stdin - docker push weaveworks/gfdatasource:$(./tools/image-tag) - fi diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 60366e05..3351a274 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ Changes * Add ``BarGuage`` for creating bar guages panels in grafana 6 * Add ``GuagePanel`` for creating guages in grafana 6 +* Removed gfdatasource - feature is built in to Grafana since v5. * TBA diff --git a/Makefile b/Makefile index 117d7778..11d174b6 100644 --- a/Makefile +++ b/Makefile @@ -58,10 +58,8 @@ deps: setup.py .ensure-tox tox.ini $(VIRTUALENV_BIN)/flake8 $(VIRTUALENV_BIN)/py.test: $(DEPS_UPTODATE) -gfdatasource/$(UPTODATE): gfdatasource/* - lint: .ensure-flake8 - $(FLAKE8) gfdatasource/gfdatasource grafanalib + $(FLAKE8) grafanalib test: .ensure-tox $(TOX) --skip-missing-interpreters diff --git a/README.rst b/README.rst index 67fe91b6..3e9cf19c 100644 --- a/README.rst +++ b/README.rst @@ -141,19 +141,12 @@ If you're working on the project, and need to build from source, it's done as fo $ . ./.env/bin/activate $ pip install -e . -`gfdatasource` -============== +Configuring Grafana Datasources +=============================== -This module also provides a script and docker image which can configure grafana -with new sources, or enable app plugins. - -The script answers the `--help` with full usage information, but basic -invocation looks like this: - -.. code-block:: console - - $ --grafana-url http://grafana. datasource --data-source-url http://datasource - $ --grafana-url http://grafana. app --id my-plugin +This repo used to contain a program `gfdatasource` for configuring +Grafana data sources, but it has been retired since Grafana now has a +built-in way to do it. See https://grafana.com/docs/administration/provisioning/#datasources Getting Help ============ diff --git a/gfdatasource/Dockerfile b/gfdatasource/Dockerfile deleted file mode 100644 index 8cfa492f..00000000 --- a/gfdatasource/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM alpine:3.10 -RUN apk add --no-cache --upgrade python3 -RUN python3 -m ensurepip && pip3 install --upgrade pip -COPY requirements.txt / -RUN pip3 install -r requirements.txt -COPY gfdatasource / -ENTRYPOINT ["/gfdatasource"] - -ARG revision -LABEL works.weave.role="system" \ - maintainer="Weaveworks " \ - org.opencontainers.image.title="grafanalib" \ - org.opencontainers.image.source="https://github.com/weaveworks/grafanalib" \ - org.opencontainers.image.revision="${revision}" \ - org.opencontainers.image.vendor="Weaveworks" diff --git a/gfdatasource/gfdatasource b/gfdatasource/gfdatasource deleted file mode 100755 index 96a8f42f..00000000 --- a/gfdatasource/gfdatasource +++ /dev/null @@ -1,260 +0,0 @@ -#!/usr/bin/env python3 - -"""Tools for maintaining Grafana datasources & application plugins.""" - -import argparse -import json -import sys -import time -from urllib.parse import ParseResult, urlparse - -import attr -import requests - - -@attr.s -class BasicAuthCredentials(object): - username = attr.ib() - password = attr.ib() - - def to_json_dict(self): - return { - 'basicAuth': True, - 'basicAuthUser': self.username, - 'basicAuthPassword': self.password - } - - -@attr.s -class DataSource(object): - """A data source for Grafana.""" - - name = attr.ib() - type = attr.ib() - url = attr.ib() - access = attr.ib() - credentials = attr.ib() - - def to_json_dict(self): - data = { - 'name': self.name, - 'type': self.type, - 'url': self.url, - 'access': self.access, - } - if self.credentials: - data.update(self.credentials.to_json_dict()) - return data - - -@attr.s -class App(object): - '''An App plugin for Grafana - - Grafana application configuration logic is entirely client side, therefore - this util cannot fill UI fields without emulating a full browser. - Therefore this must be provided with the right data to be sent to the - server to emulate the output of the client-side application. - ''' - - id = attr.ib() - json_data = attr.ib() - secure_json_data = attr.ib() - - def to_json_dict(self): - data = { - 'id': self.id, - 'enabled': True, - 'pinned': True, - 'json_data': self.json_data, - } - if self.secure_json_data is not None: - data['secure_json_data'] = self.secure_json_data - return data - - -@attr.s -class GrafanaAPI(object): - """HTTP client for Grafana's API.""" - - base_url = attr.ib() - credentials = attr.ib() - - def update_datasource(self, data_source): - return requests.post( - '/'.join([self.base_url, 'datasources']), - json=data_source.to_json_dict(), - auth=(self.credentials.username, self.credentials.password) - ) - - def update_app(self, app): - return requests.post( - '/'.join([self.base_url, 'plugins', app.id, 'settings']), - json=app.to_json_dict(), - auth=(self.credentials.username, self.credentials.password) - ) - - -def cmd_datasource(grafana_api, opts): - datasource_url, datasource_creds = _split_creds(opts.data_source_url) - datasource = DataSource( - name=opts.name, type=opts.type, access=opts.access, - url=datasource_url, credentials=datasource_creds, - ) - - grafana_api.update_datasource(datasource) - - -def cmd_app(grafana_api, opts): - json_data = json.loads(opts.json_data) - if opts.secure_json_data is not None: - secure_json_data = json.loads(opts.secure_json_data) - else: - secure_json_data = None - - app = App( - id=opts.id, json_data=json_data, secure_json_data=secure_json_data) - - grafana_api.update_app(app) - - -class DefaultSubcommandArgParse(argparse.ArgumentParser): - _default_subparser = None - - def set_default_subparser(self, name): - self._default_subparser = name - - def parse_args(self, args=None, namespace=None): - # Find the subparser for our subcommand - subparser_action = None - for action in self._subparsers._actions: - if isinstance(action, argparse._SubParsersAction): - subparser_action = action - - default = self._default_subparser - if not default or not subparser_action: - return super().parse_args(args, namespace) - - if set(args).intersection(subparser_action.choices.keys()): - # If a subcommand was specified, we don't need to do anything - return super().parse_args(args, namespace) - - # Otherwise, we need to inject the default command between the global - # args and the command args - global_options = { - option: action - for action in self._subparsers._actions - for option in action.option_strings - } - - # Ensure that global options come before command options - remaining = tuple(args) - global_args = () - cmd_args = () - while remaining: - arg, remaining = remaining[0], remaining[1:] - if arg in global_options: - global_args += (arg, ) - nargs = global_options[arg].nargs - if nargs: - global_args += tuple(remaining[0:nargs]) - remaining = remaining[nargs:] - - elif any(arg.startswith(opt + '=') - for opt in global_options.keys()): - global_args += (arg, ) - else: - cmd_args += (arg, ) - - return super().parse_args( - list(global_args + (default,) + cmd_args), namespace) - - -def make_parser(): - parser = DefaultSubcommandArgParse(prog='gfdatasource') - parser.add_argument( - '--grafana-url', type=urlparse, required=True, - help="URL of Grafana API", - ) - parser.add_argument( - '--update-interval', type=int, default=10, - help="How frequently to update Grafana, in seconds", - ) - subparsers = parser.add_subparsers(dest='cmd', help='Functions') - - ds_parser = subparsers.add_parser('datasource') - ds_parser.add_argument( - '--data-source-url', type=urlparse, required=True, - help="URL of data source", - ) - ds_parser.add_argument( - '--access', type=str, default='proxy', - help="Type of access used by Grafana to the data source", - ) - ds_parser.add_argument( - '--type', type=str, default='prometheus', - help="The type of data source", - ) - ds_parser.add_argument( - '--name', type=str, default='Prometheus', - help="The name of the data source", - ) - - app_parser = subparsers.add_parser('app') - app_parser.add_argument( - '--id', type=str, required=True, help="The app plugin ID", - ) - app_parser.add_argument( - '--json-data', type=str, default='{}', - help="JSON data ", - ) - app_parser.add_argument( - '--secure-json-data', type=str, default=None, - help="JSON data ", - ) - - # For backwards compatibility - parser.set_default_subparser('datasource') - - return parser - - -def _split_creds(url): - creds = BasicAuthCredentials(url.username, url.password) - netloc = url.netloc.split('@')[1] if '@' in url.netloc else url.netloc - url = ParseResult( - scheme=url.scheme, - netloc=netloc, - path=url.path, - params=url.params, - query=url.query, - fragment=url.fragment, - ) - return url.geturl(), creds - - -_cmds = { - 'datasource': cmd_datasource, - 'app': cmd_app, -} - - -def main(): - parser = make_parser() - opts = parser.parse_args(sys.argv[1:]) - grafana_url, grafana_creds = _split_creds(opts.grafana_url) - grafana_api = GrafanaAPI(base_url=grafana_url, credentials=grafana_creds) - - try: - cmd_func = _cmds[opts.cmd] - except KeyError: - print('Unknown command', opts.cmd) - sys.exit(1) - - while True: - cmd_func(grafana_api, opts) - time.sleep(opts.update_interval) - - -if __name__ == '__main__': - main() diff --git a/gfdatasource/requirements.txt b/gfdatasource/requirements.txt deleted file mode 100644 index 14744588..00000000 --- a/gfdatasource/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -attrs==16.3.0 -requests==2.12.3 From 7822e97202cd87c07a5ac348e62b767407a1a529 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 21 Feb 2020 10:46:29 +0100 Subject: [PATCH 050/403] generate API docs, publish on RTD closes: #4 --- .circleci/config.yml | 4 +++ .gitignore | 3 ++ .readthedocs.yml | 25 +++++++++++++++ CHANGELOG.rst | 8 ++--- Dockerfile.docs | 15 +++++++++ Makefile | 15 +++++++++ README.rst | 13 +++++--- docs/CHANGELOG.rst | 1 + docs/CONTRIBUTING.rst | 7 ++--- docs/Makefile | 20 ++++++++++++ docs/README.rst | 1 + docs/api/grafanalib.rst | 70 +++++++++++++++++++++++++++++++++++++++++ docs/api/modules.rst | 7 +++++ docs/conf.py | 54 +++++++++++++++++++++++++++++++ docs/index.rst | 29 +++++++++++++++++ docs/requirements.txt | 1 + grafanalib/core.py | 2 +- grafanalib/zabbix.py | 2 +- 18 files changed, 263 insertions(+), 14 deletions(-) create mode 100644 .readthedocs.yml create mode 100644 Dockerfile.docs create mode 120000 docs/CHANGELOG.rst create mode 100644 docs/Makefile create mode 120000 docs/README.rst create mode 100644 docs/api/grafanalib.rst create mode 100644 docs/api/modules.rst create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/requirements.txt diff --git a/.circleci/config.yml b/.circleci/config.yml index 2da8db32..fe51be46 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,6 +6,7 @@ jobs: - image: circleci/python:3.7.5 steps: - checkout + - setup_remote_docker - run: name: Dependencies command: | @@ -14,11 +15,14 @@ jobs: $HOME/.pyenv/bin/pyenv install 3.5.8 $HOME/.pyenv/bin/pyenv install 3.7.5 pip3 install --user tox flake8 + pip3 install --user -r docs/requirements.txt make deps - run: name: make all command: | make all + # Test that the documentation is okay + - run: make test-docs - store_test_results: path: test-results - store_artifacts: diff --git a/.gitignore b/.gitignore index db820628..0baf52c0 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ test-results/junit-*.xml .ensure-* /.tox /.coverage + +# Documentation +docs/build diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 00000000..2ecec439 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,25 @@ +# .readthedocs.yml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +# Required +version: 2 + +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Build documentation with MkDocs +#mkdocs: +# configuration: mkdocs.yml + +# Optionally build your docs in additional formats such as PDF and ePub +formats: all + +# Optionally set the version of Python and requirements required to build your docs +python: + version: 3.7 + install: + - requirements: docs/requirements.txt + - method: setuptools + path: . diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3351a274..daf449f7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,7 +3,7 @@ Changelog ========= Next release -======= +============ Changes ------- @@ -11,10 +11,10 @@ Changes * Add ``BarGuage`` for creating bar guages panels in grafana 6 * Add ``GuagePanel`` for creating guages in grafana 6 * Removed gfdatasource - feature is built in to Grafana since v5. -* TBA - +* Generate API docs for readthedocs.org 0.5.5 (2020-02-17) +================== It's been a while since the last release and we are happy to get this one into your hands. 0.5.5 is a maintenance release, most importantly it adds support for Python >= 3.5. @@ -35,7 +35,7 @@ Changes Many thanks to contributors @bboreham, @dholbach, @ducksecops, @kevingessner, @matthewmrichter, @uritau. 0.5.4 (2019-08-30) -======= +================== Changes ------- diff --git a/Dockerfile.docs b/Dockerfile.docs new file mode 100644 index 00000000..c8a83a15 --- /dev/null +++ b/Dockerfile.docs @@ -0,0 +1,15 @@ +FROM ddidier/sphinx-doc + +WORKDIR /doc + +COPY . . + +RUN pip3 install -r docs/requirements.txt && \ + apt update && apt install -y linkchecker && \ + python3 setup.py install && \ + cd docs && make html && \ + cd build/html/_static/fonts/RobotoSlab && \ + ln -s roboto-slab-v7-regular.eot roboto-slab.eot + + +CMD ["python", "-m", "http.server", "8000"] diff --git a/Makefile b/Makefile index 11d174b6..7a1284e3 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,8 @@ TOX := $(shell command -v tox 2> /dev/null) PIP := $(shell command -v pip3 2> /dev/null) FLAKE8 := $(shell command -v flake8 2> /dev/null) +DOCS_PORT:=8000 + .ensure-tox: .ensure-pip ifndef TOX rm -f .ensure-tox @@ -76,3 +78,16 @@ clean: clean-deps: rm -rf $(VIRTUALENV_DIR) + +update-docs-modules: + sphinx-apidoc -f grafanalib -o docs/api + +build-docs: update-docs-modules + docker build -t grafanalib-docs -f Dockerfile.docs . + +test-docs: build-docs + @docker run -it grafanalib-docs /usr/bin/linkchecker docs/build/html/index.html + +serve-docs: build-docs + @echo Starting docs website on http://localhost:${DOCS_PORT}/docs/build/html/index.html + @docker run -i -p ${DOCS_PORT}:8000 -e USER_ID=$$UID grafanalib-docs diff --git a/README.rst b/README.rst index 3e9cf19c..5d3bbbe1 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ -========== -grafanalib -========== +=============================== +Getting Started with grafanalib +=============================== .. image:: https://circleci.com/gh/weaveworks/grafanalib.svg?style=shield :target: https://circleci.com/gh/weaveworks/grafanalib @@ -102,7 +102,12 @@ percentile latency: There is a fair bit of repetition here, but once you figure out what works for your needs, you can factor that out. -See `our Weave-specific customizations `_ for inspiration. +See `our Weave-specific customizations +`_ +for inspiration. + +You can read the entire grafanlib documentation on `readthedocs.io +`_. Generating dashboards ===================== diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst new file mode 120000 index 00000000..e22698ba --- /dev/null +++ b/docs/CHANGELOG.rst @@ -0,0 +1 @@ +../CHANGELOG.rst \ No newline at end of file diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst index 5d53e0a4..1991c406 100644 --- a/docs/CONTRIBUTING.rst +++ b/docs/CONTRIBUTING.rst @@ -11,7 +11,7 @@ If something comes up during a code review or on a ticket that you think should Code of conduct =============== -We have a `code of conduct`_, and we enforce it. Please take a look! +We have a :doc:`code of conduct <../CODE_OF_CONDUCT>`, and we enforce it. Please take a look! Coding guidelines ================= @@ -55,7 +55,7 @@ Submitting a PR * We are very grateful for all PRs, and deeply appreciate the work and effort involved! * We try to review PRs as quickly as possible, but it might take a couple of weeks to get around to reviewing your PR—sorry, we know that sucks -* Please add an entry to the `CHANGELOG`_ in your PR +* Please add an entry to the :doc:`CHANGELOG <../CHANGELOG>` in your PR * It helps a lot if the PR description provides some context on what you are trying to do and why you think it's a good idea * The smaller the PR, the more quickly we'll be able to review it @@ -71,5 +71,4 @@ Filing a bug .. _`CHANGELOG`: ../CHANGELOG.rst .. _`attr.Factory`: http://www.attrs.org/en/stable/api.html#attr.Factory .. _`hypothesis`: http://hypothesis.works/ -.. _`core.py`: ../grafanalib/core.py -.. _`code of conduct`: ./CODE_OF_CONDUCT.rst +.. _`core.py`: https://github.com/weaveworks/grafanalib/blob/master/grafanalib/core.py diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..ed880990 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.rst b/docs/README.rst new file mode 120000 index 00000000..89a01069 --- /dev/null +++ b/docs/README.rst @@ -0,0 +1 @@ +../README.rst \ No newline at end of file diff --git a/docs/api/grafanalib.rst b/docs/api/grafanalib.rst new file mode 100644 index 00000000..4638337a --- /dev/null +++ b/docs/api/grafanalib.rst @@ -0,0 +1,70 @@ +grafanalib package +================== + +Submodules +---------- + +grafanalib.core module +---------------------- + +.. automodule:: grafanalib.core + :members: + :undoc-members: + :show-inheritance: + +grafanalib.elasticsearch module +------------------------------- + +.. automodule:: grafanalib.elasticsearch + :members: + :undoc-members: + :show-inheritance: + +grafanalib.opentsdb module +-------------------------- + +.. automodule:: grafanalib.opentsdb + :members: + :undoc-members: + :show-inheritance: + +grafanalib.prometheus module +---------------------------- + +.. automodule:: grafanalib.prometheus + :members: + :undoc-members: + :show-inheritance: + +grafanalib.validators module +---------------------------- + +.. automodule:: grafanalib.validators + :members: + :undoc-members: + :show-inheritance: + +grafanalib.weave module +----------------------- + +.. automodule:: grafanalib.weave + :members: + :undoc-members: + :show-inheritance: + +grafanalib.zabbix module +------------------------ + +.. automodule:: grafanalib.zabbix + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: grafanalib + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/modules.rst b/docs/api/modules.rst new file mode 100644 index 00000000..be75b7a3 --- /dev/null +++ b/docs/api/modules.rst @@ -0,0 +1,7 @@ +grafanalib +========== + +.. toctree:: + :maxdepth: 4 + + grafanalib diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..487d4846 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,54 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# + +import os +import sys +sys.path.insert(0, os.path.abspath('.')) + + +# -- Project information ----------------------------------------------------- + +project = 'grafanalib' +copyright = '2020, grafanalib community' +author = 'grafanalib community' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..7bab176c --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,29 @@ +.. grafanalib documentation master file, created by + sphinx-quickstart on Mon Feb 17 14:29:44 2020. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to grafanalib's documentation! +====================================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + ../README + api/grafanalib + api/modules + + CONTRIBUTING + CODE_OF_CONDUCT + releasing + + CHANGELOG + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 00000000..06829942 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinx == 2.4.1 diff --git a/grafanalib/core.py b/grafanalib/core.py index 5d041c1c..0ffaf4fa 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1568,7 +1568,7 @@ def with_styled_columns(cls, columns, styles=None, **kwargs): The ColumnStyle may also be None. :param styles: An optional list of extra column styles that will be appended to the table's list of styles. - :param **kwargs: Other parameters to the Table constructor. + :param kwargs: Other parameters to the Table constructor. :return: A Table. """ extraStyles = styles if styles else [] diff --git a/grafanalib/zabbix.py b/grafanalib/zabbix.py index 3fb9038c..8939c7aa 100644 --- a/grafanalib/zabbix.py +++ b/grafanalib/zabbix.py @@ -271,7 +271,7 @@ class ZabbixAggregateByFunction(object): Takes all timeseries and consolidate all its points falled in given interval into one point using function, which can be one of: - avg, min, max, median. + avg, min, max, median. http://docs.grafana-zabbix.org/reference/functions/#aggregateBy """ From 6f9a28c444f1855dd492a2c9fbc3e88fcae3f877 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Wed, 4 Mar 2020 17:13:04 -0500 Subject: [PATCH 051/403] The Time Series transform yields a column named "Time" - the pattern here had lower case "time" and as such did not apply the pattern, at least in my scenario. --- grafanalib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index b5d1fcb5..460a70f5 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1552,7 +1552,7 @@ def styles_default(self): return [ ColumnStyle( alias="Time", - pattern="time", + pattern="Time", type=DateColumnStyleType(), ), ColumnStyle( From 0d0ad990a3abbe0869f2c645575ed937bbc99b33 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Wed, 4 Mar 2020 17:14:59 -0500 Subject: [PATCH 052/403] Changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 044516ee..c462f19d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ Changes * Removed gfdatasource - feature is built in to Grafana since v5. * Generate API docs for readthedocs.org * Fix AlertList panel generation +* Fix lowercase `"time"` pattern for default time_series column format in Table class 0.5.5 (2020-02-17) ================== From c8a2cd2672f84c762d00ed6bd88b7738abe6eaa0 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Thu, 5 Mar 2020 08:32:59 -0500 Subject: [PATCH 053/403] Adding back lowercase t as well. --- grafanalib/core.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index 460a70f5..92a38f1c 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1555,6 +1555,11 @@ def styles_default(self): pattern="Time", type=DateColumnStyleType(), ), + ColumnStyle( + alias="Time", + pattern="time", + type=DateColumnStyleType(), + ), ColumnStyle( pattern="/.*/", ), From 97bbff3cd369e3c0ecea381d036cf4f003ce2619 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Thu, 5 Mar 2020 08:34:41 -0500 Subject: [PATCH 054/403] Lowercase the alias --- grafanalib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 92a38f1c..53d7a96d 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1556,7 +1556,7 @@ def styles_default(self): type=DateColumnStyleType(), ), ColumnStyle( - alias="Time", + alias="time", pattern="time", type=DateColumnStyleType(), ), From 30b5ca64c5069fbc95482e0567bccbbd6be0570c Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 6 Mar 2020 16:48:17 +0100 Subject: [PATCH 055/403] Drop test support for python 2.7. --- .circleci/config.yml | 1 - CHANGELOG.rst | 2 ++ README.rst | 2 +- tox.ini | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fe51be46..39bb9614 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -11,7 +11,6 @@ jobs: name: Dependencies command: | git clone --depth 1 -b v1.2.15 https://github.com/pyenv/pyenv.git $HOME/.pyenv - $HOME/.pyenv/bin/pyenv install 2.7.16 $HOME/.pyenv/bin/pyenv install 3.5.8 $HOME/.pyenv/bin/pyenv install 3.7.5 pip3 install --user tox flake8 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 044516ee..52faed76 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,8 @@ Changes * Removed gfdatasource - feature is built in to Grafana since v5. * Generate API docs for readthedocs.org * Fix AlertList panel generation +* Drop testing of Python 2.7, it has been EOL'ed and CI was broken + due to this. 0.5.5 (2020-02-17) ================== diff --git a/README.rst b/README.rst index 5d3bbbe1..c881bb55 100644 --- a/README.rst +++ b/README.rst @@ -134,7 +134,7 @@ Support This library is in its very early stages. We'll probably make changes that break backwards compatibility, although we'll try hard not to. -grafanalib works with Python 2.7, 3.4, 3.5, 3.6 and 3.7. +grafanalib works with Python 3.4, 3.5, 3.6 and 3.7. Developing ========== diff --git a/tox.ini b/tox.ini index d2e2f185..7b81fb3f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py35, py37 +envlist = py35, py37 [testenv] commands = pytest --junitxml=test-results/junit-{envname}.xml From 8d91d57890ea2e30b8f70081bbaeb00d10052f3b Mon Sep 17 00:00:00 2001 From: jaychitalia95 Date: Sat, 7 Mar 2020 22:48:43 -0500 Subject: [PATCH 056/403] Added all parameters for StringColumnStyle for table --- grafanalib/core.py | 65 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index b5d1fcb5..cd21ed87 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1353,6 +1353,30 @@ def to_json_data(self): 'timeFrom': self.timeFrom, } +@attr.s +class TableRangeMaps(object): + fr = attr.ib(default = "") + to = attr.ib( default = "") + text = attr.ib(validator=instance_of(str), default = "") + + def to_json_data(self): + return { + 'from': self.fr, + 'to': self.to, + 'text': self.text + } + + +@attr.s +class TableValueMaps(object): + value = attr.ib(default = "") + text = attr.ib(validator=instance_of(str), default = "") + + def to_json_data(self): + return { + 'value': self.value, + 'text': self.text + } @attr.s class DateColumnStyleType(object): @@ -1391,12 +1415,39 @@ def to_json_data(self): @attr.s class StringColumnStyleType(object): TYPE = 'string' + decimals = attr.ib(default = 2, validator=instance_of(int)) + colorMode = attr.ib(default=None) + colors = attr.ib(default=attr.Factory(lambda: [GREEN, ORANGE, RED])) + thresholds = attr.ib(default = attr.Factory(list)) + preserveFormat = attr.ib(validator=instance_of(bool), default = False) + sanitize = attr.ib(validator=instance_of(bool), default = False) + unit = attr.ib(default=SHORT_FORMAT) + mappingType = attr.ib(default=MAPPING_TYPE_VALUE_TO_TEXT) + valueMaps = attr.ib(validator=instance_of(list)) + rangeMaps = attr.ib(validator=instance_of(list)) - preserveFormat = attr.ib(validator=instance_of(bool)) - sanitize = attr.ib(validator=instance_of(bool)) + @valueMaps.default + def valueMaps_default(self): + return [ + TableValueMaps() + ] + + @rangeMaps.default + def rangeMaps_default(self): + return [ + TableRangeMaps() + ] def to_json_data(self): return { + 'decimals' : self.decimals, + 'colorMode' : self.colorMode, + 'colors' : self.colors, + 'thresholds' : self.thresholds, + 'unit' : self.unit, + 'mappingType' : self.mappingType, + 'valueMaps' : self.valueMaps, + 'rangeMaps' : self.rangeMaps, 'preserveFormat': self.preserveFormat, 'sanitize': self.sanitize, 'type': self.TYPE, @@ -1418,6 +1469,11 @@ class ColumnStyle(object): alias = attr.ib(default="") pattern = attr.ib(default="") + align = attr.ib(default = "auto", validator=in_(["auto", "left", "right", "center"])) + link = attr.ib(validator=instance_of(bool), default = False) + linkOpenInNewTab = attr.ib(validator=instance_of(bool), default=False) + linkUrl = attr.ib(validator=instance_of(str), default="") + linkTooltip = attr.ib(validator=instance_of(str), default="") type = attr.ib( default=attr.Factory(NumberColumnStyleType), validator=instance_of(( @@ -1432,6 +1488,11 @@ def to_json_data(self): data = { 'alias': self.alias, 'pattern': self.pattern, + 'align': self.align, + 'link': self.link, + 'linkTargetBlank': self.linkOpenInNewTab, + 'linkUrl': self.linkUrl, + 'linkTooltip': self.linkTooltip, } data.update(self.type.to_json_data()) return data From c47bbe8b7f3f6d712d248c44ce80cff05aba7732 Mon Sep 17 00:00:00 2001 From: jaychitalia95 Date: Sun, 8 Mar 2020 01:45:06 -0500 Subject: [PATCH 057/403] Minor change --- grafanalib/core.py | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index cd21ed87..d301a7bd 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1356,8 +1356,8 @@ def to_json_data(self): @attr.s class TableRangeMaps(object): fr = attr.ib(default = "") - to = attr.ib( default = "") - text = attr.ib(validator=instance_of(str), default = "") + to = attr.ib(default = "") + text = attr.ib(default = "", validator=instance_of(str)) def to_json_data(self): return { @@ -1370,7 +1370,7 @@ def to_json_data(self): @attr.s class TableValueMaps(object): value = attr.ib(default = "") - text = attr.ib(validator=instance_of(str), default = "") + text = attr.ib(default = "", validator=instance_of(str)) def to_json_data(self): return { @@ -1423,20 +1423,10 @@ class StringColumnStyleType(object): sanitize = attr.ib(validator=instance_of(bool), default = False) unit = attr.ib(default=SHORT_FORMAT) mappingType = attr.ib(default=MAPPING_TYPE_VALUE_TO_TEXT) - valueMaps = attr.ib(validator=instance_of(list)) - rangeMaps = attr.ib(validator=instance_of(list)) - - @valueMaps.default - def valueMaps_default(self): - return [ - TableValueMaps() - ] - - @rangeMaps.default - def rangeMaps_default(self): - return [ - TableRangeMaps() - ] + valueMaps = attr.ib(default = attr.Factory( + lambda: [TableValueMaps()]), validator=instance_of(list)) + rangeMaps = attr.ib(default = attr.Factory( + lambda: [TableRangeMaps()]), validator=instance_of(list)) def to_json_data(self): return { From 28ec57bb9d48a26d61e2343c56ff314ddf501bf6 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 9 Mar 2020 10:48:53 +0100 Subject: [PATCH 058/403] document next meeting --- README.rst | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 5d3bbbe1..d8649661 100644 --- a/README.rst +++ b/README.rst @@ -149,12 +149,24 @@ If you're working on the project, and need to build from source, it's done as fo Configuring Grafana Datasources =============================== -This repo used to contain a program `gfdatasource` for configuring +This repo used to contain a program ``gfdatasource`` for configuring Grafana data sources, but it has been retired since Grafana now has a built-in way to do it. See https://grafana.com/docs/administration/provisioning/#datasources +Community +========= + +We'd like you to join the ``grafanalib`` community! Talk to us on Slack (see the links), +or join us for one of our next meetings): + +- Next meeting: 2020-03-20 15:00 UTC +- https://zoom.us/j/405935052 +- `Meeting minutes and agenda + `_ + + Getting Help -============ +------------ If you have any questions about, feedback for or problems with ``grafanalib``: From 725ef66e6ac7b73049c0b0e198bbb625dc57290d Mon Sep 17 00:00:00 2001 From: Franz Schwartau Date: Fri, 4 Oct 2019 12:54:54 +0200 Subject: [PATCH 059/403] Add description attribute to Dashboard. --- grafanalib/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index b5d1fcb5..d9a71986 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -897,6 +897,7 @@ class Dashboard(object): default=attr.Factory(Annotations), validator=instance_of(Annotations), ) + description = attr.ib(default="", validator=instance_of(str)) editable = attr.ib( default=True, validator=instance_of(bool), @@ -960,6 +961,7 @@ def to_json_data(self): return { '__inputs': self.inputs, 'annotations': self.annotations, + "description": self.description, 'editable': self.editable, 'gnetId': self.gnetId, 'hideControls': self.hideControls, From 2a573c769a28cb468afb4306d7fe016161cc21bd Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 23 Mar 2020 10:26:59 +0100 Subject: [PATCH 060/403] update meeting information --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7ae4f931..057dd31f 100644 --- a/README.rst +++ b/README.rst @@ -159,10 +159,11 @@ Community We'd like you to join the ``grafanalib`` community! Talk to us on Slack (see the links), or join us for one of our next meetings): -- Next meeting: 2020-03-20 15:00 UTC +- Meetings take place monthly: third Friday of the month 15:00 UTC - https://zoom.us/j/405935052 - `Meeting minutes and agenda `_ + (includes links to meeting recordings) Getting Help From 1b07473244b36b236f6fd6193b60543e4f596323 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 20 Mar 2020 17:10:59 +0100 Subject: [PATCH 061/403] Test examples automatically - Add python3.8 (current on Ubuntu 20.04) to tox and CircleCI - decouple main github README from grafanalib docs RTD where we can use sphinx' "literalinclude" - fix flake linter issues - make examples run at all - test examples as part of general tests fixes: #55 --- .circleci/config.yml | 5 +- .flake8 | 2 + README.rst | 123 ++++-------------- docs/README.rst | 1 - docs/getting-started.rst | 75 +++++++++++ docs/index.rst | 3 +- grafanalib/elasticsearch.py | 18 +-- .../example-elasticsearch.dashboard.py | 13 +- .../tests/examples}/example.dashboard.py | 8 +- grafanalib/tests/test_examples.py | 23 ++++ tox.ini | 2 +- 11 files changed, 153 insertions(+), 120 deletions(-) create mode 100644 .flake8 delete mode 120000 docs/README.rst create mode 100644 docs/getting-started.rst rename {docs => grafanalib/tests/examples}/example-elasticsearch.dashboard.py (89%) rename {docs => grafanalib/tests/examples}/example.dashboard.py (92%) create mode 100644 grafanalib/tests/test_examples.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 39bb9614..11c91596 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,16 +3,17 @@ version: 2 jobs: build: docker: - - image: circleci/python:3.7.5 + - image: circleci/python:3.8.2 steps: - checkout - setup_remote_docker - run: name: Dependencies command: | - git clone --depth 1 -b v1.2.15 https://github.com/pyenv/pyenv.git $HOME/.pyenv + git clone --depth 1 -b v1.2.17 https://github.com/pyenv/pyenv.git $HOME/.pyenv $HOME/.pyenv/bin/pyenv install 3.5.8 $HOME/.pyenv/bin/pyenv install 3.7.5 + $HOME/.pyenv/bin/pyenv install 3.8.2 pip3 install --user tox flake8 pip3 install --user -r docs/requirements.txt make deps diff --git a/.flake8 b/.flake8 new file mode 100644 index 00000000..e44b8108 --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +ignore = E501 diff --git a/README.rst b/README.rst index 7ae4f931..fec44392 100644 --- a/README.rst +++ b/README.rst @@ -11,122 +11,43 @@ so, grafanalib is for you. grafanalib lets you generate Grafana dashboards from simple Python scripts. -Writing dashboards -================== - -The following will configure a dashboard with a single row, with one QPS graph -broken down by status code and another latency graph showing median and 99th -percentile latency: - -.. code-block:: python - - from grafanalib.core import * - - - dashboard = Dashboard( - title="Frontend Stats", - rows=[ - Row(panels=[ - Graph( - title="Frontend QPS", - dataSource='My Prometheus', - targets=[ - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"1.."}[1m]))', - legendFormat="1xx", - refId='A', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"2.."}[1m]))', - legendFormat="2xx", - refId='B', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"3.."}[1m]))', - legendFormat="3xx", - refId='C', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"4.."}[1m]))', - legendFormat="4xx", - refId='D', - ), - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', - legendFormat="5xx", - refId='E', - ), - ], - yAxes=YAxes( - YAxis(format=OPS_FORMAT), - YAxis(format=SHORT_FORMAT), - ), - alert=Alert( - name="Too many 500s on Nginx", - message="More than 5 QPS of 500s on Nginx for 5 minutes", - alertConditions=[ - AlertCondition( - Target( - expr='sum(irate(nginx_http_requests_total{job="default/frontend",status=~"5.."}[1m]))', - legendFormat="5xx", - refId='A', - ), - timeRange=TimeRange("5m", "now"), - evaluator=GreaterThan(5), - operator=OP_AND, - reducerType=RTYPE_SUM, - ), - ], - ) - ), - Graph( - title="Frontend latency", - dataSource='My Prometheus', - targets=[ - Target( - expr='histogram_quantile(0.5, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', - legendFormat="0.5 quantile", - refId='A', - ), - Target( - expr='histogram_quantile(0.99, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', - legendFormat="0.99 quantile", - refId='B', - ), - ], - yAxes=single_y_axis(format=SECONDS_FORMAT), - ), - ]), - ], - ).auto_panel_ids() - -There is a fair bit of repetition here, but once you figure out what works for -your needs, you can factor that out. +How it works +============ + +Take a look at `the examples directory +`_, +e.g. ` this dashboard +`_ +will configure a dashboard with a single row, with one QPS graph broken down +by status code and another latency graph showing median and 99th percentile +latency. + +In the code is a fair bit of repetition here, but once you figure out what +works for your needs, you can factor that out. See `our Weave-specific customizations `_ for inspiration. You can read the entire grafanlib documentation on `readthedocs.io -`_. +`_. -Generating dashboards -===================== +Getting started +=============== -If you save the above as ``frontend.dashboard.py`` (the suffix must be -``.dashboard.py``), you can then generate the JSON dashboard with: +grafanalib is just a Python package, so: .. code-block:: console - $ generate-dashboard -o frontend.json frontend.dashboard.py + $ pip install grafanalib -Installation -============ -grafanalib is just a Python package, so: +Generate the JSON dashboard like so: .. code-block:: console - $ pip install grafanalib + $ curl https://raw.githubusercontent.com/weaveworks/grafanalib/master/grafanalib/tests/examples/example.dashboard.py + $ generate-dashboard -o frontend.json example.dashboard.py + Support ======= diff --git a/docs/README.rst b/docs/README.rst deleted file mode 120000 index 89a01069..00000000 --- a/docs/README.rst +++ /dev/null @@ -1 +0,0 @@ -../README.rst \ No newline at end of file diff --git a/docs/getting-started.rst b/docs/getting-started.rst new file mode 100644 index 00000000..9beacfc7 --- /dev/null +++ b/docs/getting-started.rst @@ -0,0 +1,75 @@ +=============================== +Getting Started with grafanalib +=============================== + +.. image:: https://circleci.com/gh/weaveworks/grafanalib.svg?style=shield + :target: https://circleci.com/gh/weaveworks/grafanalib + +Do you like `Grafana `_ but wish you could version your +dashboard configuration? Do you find yourself repeating common patterns? If +so, grafanalib is for you. + +grafanalib lets you generate Grafana dashboards from simple Python scripts. + +Writing dashboards +================== + +The following will configure a dashboard with a single row, with one QPS graph +broken down by status code and another latency graph showing median and 99th +percentile latency: + +.. literalinclude:: example.dashboard.py + :language: python + +There is a fair bit of repetition here, but once you figure out what works for +your needs, you can factor that out. +See `our Weave-specific customizations +`_ +for inspiration. + +You can read the entire grafanlib documentation on `readthedocs.io +`_. + +Generating dashboards +===================== + +If you save the above as ``frontend.dashboard.py`` (the suffix must be +``.dashboard.py``), you can then generate the JSON dashboard with: + +.. code-block:: console + + $ generate-dashboard -o frontend.json frontend.dashboard.py + +Installation +============ + +grafanalib is just a Python package, so: + +.. code-block:: console + + $ pip install grafanalib + +Support +======= + +This library is in its very early stages. We'll probably make changes that +break backwards compatibility, although we'll try hard not to. + +grafanalib works with Python 3.4, 3.5, 3.6 and 3.7. + +Developing +========== +If you're working on the project, and need to build from source, it's done as follows: + +.. code-block:: console + + $ virtualenv .env + $ . ./.env/bin/activate + $ pip install -e . + +Configuring Grafana Datasources +=============================== + +This repo used to contain a program ``gfdatasource`` for configuring +Grafana data sources, but it has been retired since Grafana now has a +built-in way to do it. See https://grafana.com/docs/administration/provisioning/#datasources diff --git a/docs/index.rst b/docs/index.rst index 7bab176c..81e6c49c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,7 +10,8 @@ Welcome to grafanalib's documentation! :maxdepth: 2 :caption: Contents: - ../README + getting-started + api/grafanalib api/modules diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index e304b5e5..16eb43ab 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -108,9 +108,9 @@ class Filter(object): def to_json_data(self): return { - 'label': self.label, - 'query': self.query, - } + 'label': self.label, + 'query': self.query, + } @attr.s @@ -127,12 +127,12 @@ class FiltersGroupBy(object): def to_json_data(self): return { - 'id': str(self.id), - 'settings': { - 'filters': self.filters, - }, - 'type': 'filters', - } + 'id': str(self.id), + 'settings': { + 'filters': self.filters, + }, + 'type': 'filters', + } @attr.s diff --git a/docs/example-elasticsearch.dashboard.py b/grafanalib/tests/examples/example-elasticsearch.dashboard.py similarity index 89% rename from docs/example-elasticsearch.dashboard.py rename to grafanalib/tests/examples/example-elasticsearch.dashboard.py index 8c72cdea..8932ac52 100644 --- a/docs/example-elasticsearch.dashboard.py +++ b/grafanalib/tests/examples/example-elasticsearch.dashboard.py @@ -7,8 +7,15 @@ - Max. response time per point of time of HTTP requests """ -from grafanalib.core import * -from grafanalib.elasticsearch import * +from grafanalib.core import ( + Dashboard, Graph, Legend, NULL_AS_NULL, Row, SECONDS_FORMAT, + SHORT_FORMAT, YAxes, YAxis +) + +from grafanalib.elasticsearch import ( + DateHistogramGroupBy, ElasticsearchTarget, Filter, + FiltersGroupBy, MaxMetricAgg +) suc_label = "Success (200-300)" clt_err_label = "Client Errors (400-500)" @@ -69,7 +76,7 @@ "color": "#447EBC" }, ], - yAxes=G.YAxes( + yAxes=YAxes( YAxis( label="Count", format=SHORT_FORMAT, diff --git a/docs/example.dashboard.py b/grafanalib/tests/examples/example.dashboard.py similarity index 92% rename from docs/example.dashboard.py rename to grafanalib/tests/examples/example.dashboard.py index da25ef55..bbad2c82 100644 --- a/docs/example.dashboard.py +++ b/grafanalib/tests/examples/example.dashboard.py @@ -1,4 +1,8 @@ -from grafanalib.core import * +from grafanalib.core import ( + Alert, AlertCondition, Dashboard, Graph, + GreaterThan, OP_AND, OPS_FORMAT, Row, RTYPE_SUM, SECONDS_FORMAT, + SHORT_FORMAT, single_y_axis, Target, TimeRange, YAxes, YAxis +) dashboard = Dashboard( @@ -35,7 +39,7 @@ refId='E', ), ], - yAxes=G.YAxes( + yAxes=YAxes( YAxis(format=OPS_FORMAT), YAxis(format=SHORT_FORMAT), ), diff --git a/grafanalib/tests/test_examples.py b/grafanalib/tests/test_examples.py new file mode 100644 index 00000000..d23f6bd4 --- /dev/null +++ b/grafanalib/tests/test_examples.py @@ -0,0 +1,23 @@ +'''Run examples.''' + +from contextlib import redirect_stdout +import glob +import io +import os + +from grafanalib import _gen + + +def test_examples(): + '''Run examples in ./examples directory.''' + + examples_dir = os.path.join(os.path.dirname(__file__), 'examples') + examples = glob.glob('{}/*.dashboard.py'.format(examples_dir)) + assert len(examples) == 2 + + stdout = io.StringIO() + for example in examples: + with redirect_stdout(stdout): + ret = _gen.generate_dashboard([example]) + assert ret == 0 + assert stdout.getvalue() != '' diff --git a/tox.ini b/tox.ini index 7b81fb3f..73b55cb9 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py35, py37 +envlist = py35, py37, py38 [testenv] commands = pytest --junitxml=test-results/junit-{envname}.xml From de0e00f7f6cee96164ecc94bbbef9fbc7943e815 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Wed, 18 Mar 2020 14:54:04 +0100 Subject: [PATCH 062/403] Link to docs more prominently fixes: #214 --- README.rst | 6 ++++++ setup.py | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/README.rst b/README.rst index fec44392..34ac0f95 100644 --- a/README.rst +++ b/README.rst @@ -5,6 +5,11 @@ Getting Started with grafanalib .. image:: https://circleci.com/gh/weaveworks/grafanalib.svg?style=shield :target: https://circleci.com/gh/weaveworks/grafanalib +.. image:: https://readthedocs.org/projects/grafanalib/badge/?version=latest + :alt: Documentation Status + :scale: 100% + :target: https://grafanalib.readthedocs.io/en/latest/?badge=latest + Do you like `Grafana `_ but wish you could version your dashboard configuration? Do you find yourself repeating common patterns? If so, grafanalib is for you. @@ -91,6 +96,7 @@ Getting Help If you have any questions about, feedback for or problems with ``grafanalib``: +- Read the documentation at https://grafanalib.readthedocs.io - Invite yourself to the `Weave Users Slack `_. - Ask a question on the `#grafanalib `_ slack channel. - `File an issue `_. diff --git a/setup.py b/setup.py index e253eb7b..162b6c81 100644 --- a/setup.py +++ b/setup.py @@ -19,6 +19,10 @@ def local_file(name): description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', + project_urls={ + "Documentation": "https://grafanalib.readthedocs.io", + "Source": "https://github.com/weaveworks/grafanalib", + }, author='Weaveworks', author_email='help+grafanalib@weave.works', license='Apache', From ab33a77ccabd7832063e8f7fadde72823cc69ee5 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 23 Mar 2020 14:01:11 +0100 Subject: [PATCH 063/403] Fix example showing up - add '-W' to docs build to make sure we get references right - fix docstrings --- docs/Makefile | 2 +- docs/getting-started.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index ed880990..d912a3f6 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line, and also # from the environment for the first two. -SPHINXOPTS ?= +SPHINXOPTS ?= -W SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = build diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 9beacfc7..5b5133d7 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -18,7 +18,7 @@ The following will configure a dashboard with a single row, with one QPS graph broken down by status code and another latency graph showing median and 99th percentile latency: -.. literalinclude:: example.dashboard.py +.. literalinclude:: ../grafanalib/tests/examples/example.dashboard.py :language: python There is a fair bit of repetition here, but once you figure out what works for From 969f5854cc9d2f3815e4518a1ae18f92ee5bb8be Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 23 Mar 2020 14:49:01 +0100 Subject: [PATCH 064/403] fix docstrings --- grafanalib/core.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 44d3a66f..5d26a743 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1590,7 +1590,7 @@ def with_styled_columns(cls, columns, styles=None, **kwargs): """Construct a table where each column has an associated style. :param columns: A list of (Column, ColumnStyle) pairs, where the - ColumnStyle is the style for the column and does **not** have a + ColumnStyle is the style for the column and does not have a pattern set (or the pattern is set to exactly the column name). The ColumnStyle may also be None. :param styles: An optional list of extra column styles that will be @@ -1654,6 +1654,7 @@ def to_json_data(self): @attr.s class BarGauge(object): """Generates Bar Gauge panel json structure + :param allValue: If All values should be shown or a Calculation :param cacheTimeout: metric query result cache ttl :param calc: Calculation to perform on metrics @@ -1791,6 +1792,7 @@ def to_json_data(self): @attr.s class GaugePanel(object): """Generates Gauge panel json structure + :param allValue: If All values should be shown or a Calculation :param cacheTimeout: metric query result cache ttl :param calc: Calculation to perform on metrics @@ -1808,7 +1810,7 @@ class GaugePanel(object): :param links: additional web links :param max: maximum value of the gauge :param maxDataPoints: maximum metric query results, - that will be used for rendering + that will be used for rendering :param min: minimum value of the gauge :param minSpan: minimum span number :param rangeMaps: the list of value to text mappings From 20bcecce49fee635ea11138ebfb2b26e50e40ee7 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 23 Mar 2020 14:53:27 +0100 Subject: [PATCH 065/403] fix missing directory --- Dockerfile.docs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile.docs b/Dockerfile.docs index c8a83a15..996000b3 100644 --- a/Dockerfile.docs +++ b/Dockerfile.docs @@ -7,7 +7,7 @@ COPY . . RUN pip3 install -r docs/requirements.txt && \ apt update && apt install -y linkchecker && \ python3 setup.py install && \ - cd docs && make html && \ + cd docs && mkdir _static && make html && \ cd build/html/_static/fonts/RobotoSlab && \ ln -s roboto-slab-v7-regular.eot roboto-slab.eot From 88b4144d0398286fd21961807fc8d2db9b2fb55f Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 23 Mar 2020 15:07:37 +0100 Subject: [PATCH 066/403] fix link in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 13ebc62f..89ab4efe 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ How it works Take a look at `the examples directory `_, -e.g. ` this dashboard +e.g. `this dashboard `_ will configure a dashboard with a single row, with one QPS graph broken down by status code and another latency graph showing median and 99th percentile From 97911e68ca29a5a25fa7c2d97cf44db417f7f513 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 23 Mar 2020 15:13:37 +0100 Subject: [PATCH 067/403] remove irrelevant pointer --- docs/getting-started.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 5b5133d7..6ff6c44a 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -27,9 +27,6 @@ See `our Weave-specific customizations `_ for inspiration. -You can read the entire grafanlib documentation on `readthedocs.io -`_. - Generating dashboards ===================== From c29b216a9c94f27c40a8b99f5bc5dd282da8d44a Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 23 Mar 2020 15:26:33 +0100 Subject: [PATCH 068/403] update changelog --- CHANGELOG.rst | 5 +++++ README.rst | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52faed76..f8e78c82 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,11 @@ Changes * Fix AlertList panel generation * Drop testing of Python 2.7, it has been EOL'ed and CI was broken due to this. +* Automatically test documentation examples. +* Point to dev meeting resources. +* Add description attribute to Dashboard. +* Add support for custom variables. +* Point out documentation on readthedocs more clearly. 0.5.5 (2020-02-17) ================== diff --git a/README.rst b/README.rst index 89ab4efe..e0d030a4 100644 --- a/README.rst +++ b/README.rst @@ -60,7 +60,7 @@ Support This library is in its very early stages. We'll probably make changes that break backwards compatibility, although we'll try hard not to. -grafanalib works with Python 3.4, 3.5, 3.6 and 3.7. +grafanalib works with Python 3.4 through 3.8. Developing ========== From d377a98690836aa090fd822721ca31f1bb74c63e Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Fri, 27 Mar 2020 16:09:40 -0400 Subject: [PATCH 069/403] Add Heatmap support - initial version --- grafanalib/core.py | 131 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index ff1836dd..5d452e13 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -77,6 +77,7 @@ def to_json_data(self): ALERTLIST_TYPE = "alertlist" BARGAUGE_TYPE = "bargauge" GAUGE_TYPE = "gauge" +HEATMAP_TYPE = "heatmap" DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -1912,3 +1913,133 @@ def to_json_data(self): "transparent": self.transparent, "type": GAUGE_TYPE, } + +@attr.s +class HeatmapColor(object): + """A Color object for heatmaps + + :param cardColor + :param colorScale + :param colorScheme + :param exponent + :param max + :param min + :param mode + """ + + # Maybe cardColor should validate to RGBA object, not sure + cardColor = attr.ib(default='#b4ff00', validator=instance_of(str)) + colorScale = attr.ib(default='sqrt', validator=instance_of(str)) + colorScheme = attr.ib(default='interpolateOranges') + exponent = attr.ib(default=0.5, validator=instance_of(float)) + mode = attr.ib(default="spectrum", validator=instance_of(str)) + max = attr.ib(default=None) + min = attr.ib(default=None) + + + def to_json_data(self): + return { + "mode": self.mode, + "cardColor": self.cardColor, + "colorScale": self.colorScale, + "exponent": self.exponent, + "colorScheme": self.colorScheme, + "max": self.max, + "min": self.min, + } + +@attr.s +class Heatmap(object): + """Generates Heatmap panel json structure + (https://grafana.com/docs/grafana/latest/features/panels/heatmap/) + + :param heatmap + :param cards: A heatmap card object: containing: + "cardPadding" + "cardRound" + :param color: Heatmap color object + :param dataFormat: 'timeseries' or 'tsbuckets' + :param yBucketBound: 'auto', 'upper', 'middle', 'lower' + :param reverseYBuckets: boolean + :param xBucketSize + :param xBucketNumber + :param yBucketSize + :param yBucketNumber + :param highlightCards: boolean + :param hideZeroBuckets: boolean + """ + + title = attr.ib() + description = attr.ib(default=None) + id = attr.ib(default=None) + # The below does not really like the Legend class we have defined above + legend = attr.ib(default={"show": False}) + links = attr.ib(default=None) + targets = attr.ib(default=None) + tooltip = attr.ib( + default=attr.Factory(Tooltip), + validator=instance_of(Tooltip), + ) + span = attr.ib(default=None) + + cards = attr.ib(default={ + "cardPadding": None, + "cardRound": None + }) + + color = attr.ib( + default=attr.Factory(HeatmapColor), + validator=instance_of(HeatmapColor), + ) + + dataFormat = attr.ib(default='timeseries') + datasource = attr.ib(default=None) + heatmap = {} + hideZeroBuckets = attr.ib(default=False) + highlightCards = attr.ib(default=True) + options = attr.ib(default=None) + + xAxis = attr.ib( + default=attr.Factory(XAxis), + validator=instance_of(XAxis) + ) + xBucketNumber = attr.ib(default=None) + xBucketSize = attr.ib(default=None) + + yAxis = attr.ib( + default=attr.Factory(YAxis), + validator=instance_of(YAxis) + ) + yBucketBound = attr.ib(default=None) + yBucketNumber = attr.ib(default=None) + yBucketSize = attr.ib(default=None) + reverseYBuckets = attr.ib(default=False) + + def to_json_data(self): + return { + 'cards': self.cards, + 'color': self.color, + 'dataFormat': self.dataFormat, + 'datasource': self.datasource, + 'description': self.description, + 'heatmap': self.heatmap, + 'hideZeroBuckets': self.hideZeroBuckets, + 'highlightCards': self.highlightCards, + 'id': self.id, + 'legend': self.legend, + 'links': self.links, + 'options': self.options, + 'reverseYBuckets': self.reverseYBuckets, + 'span': self.span, + 'targets': self.targets, + 'title': self.title, + 'tooltip': self.tooltip, + 'type': HEATMAP_TYPE, + 'xAxis': self.xAxis, + 'xBucketNumber': self.xBucketNumber, + 'xBucketSize': self.xBucketSize, + 'yAxis': self.yAxis, + 'yBucketBound': self.yBucketBound, + 'yBucketNumber': self.yBucketNumber, + 'yBucketSize': self.yBucketSize + } From a5c155cb8e49b2fbc710b3620e8ecc29d24af326 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Fri, 27 Mar 2020 16:13:29 -0400 Subject: [PATCH 070/403] Changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 31832e8e..d1ea4c1a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ Next release Changes ------- +* Add ``Heatmap`` class (and ``HeatmapColor``) to support the Heatmap panel (#170) * Add ``BarGuage`` for creating bar guages panels in grafana 6 * Add ``GuagePanel`` for creating guages in grafana 6 * Removed gfdatasource - feature is built in to Grafana since v5. From f0a5331fa95056115f34c2bd7a2d92e37a113264 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Fri, 27 Mar 2020 16:37:30 -0400 Subject: [PATCH 071/403] CircleCI style related tweaks --- grafanalib/core.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 5d452e13..e0b18cae 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1914,6 +1914,7 @@ def to_json_data(self): "type": GAUGE_TYPE, } + @attr.s class HeatmapColor(object): """A Color object for heatmaps @@ -1948,6 +1949,7 @@ def to_json_data(self): "min": self.min, } + @attr.s class Heatmap(object): """Generates Heatmap panel json structure @@ -1985,7 +1987,8 @@ class Heatmap(object): cards = attr.ib(default={ "cardPadding": None, "cardRound": None - }) + } + ) color = attr.ib( default=attr.Factory(HeatmapColor), From 065ca7a0d0564e5b3823329327c04928baf5b99c Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Fri, 27 Mar 2020 16:49:52 -0400 Subject: [PATCH 072/403] More circleci styling --- grafanalib/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index e0b18cae..01901ae0 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1937,7 +1937,6 @@ class HeatmapColor(object): max = attr.ib(default=None) min = attr.ib(default=None) - def to_json_data(self): return { "mode": self.mode, @@ -1984,9 +1983,10 @@ class Heatmap(object): ) span = attr.ib(default=None) - cards = attr.ib(default={ - "cardPadding": None, - "cardRound": None + cards = attr.ib( + default={ + "cardPadding": None, + "cardRound": None } ) From 565072cd3ced7bcf6afcbc1d86abe4d97de8d511 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Fri, 27 Mar 2020 17:00:27 -0400 Subject: [PATCH 073/403] Fix docstring hopefully --- grafanalib/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 01901ae0..eb0929f7 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1951,8 +1951,7 @@ def to_json_data(self): @attr.s class Heatmap(object): - """Generates Heatmap panel json structure - (https://grafana.com/docs/grafana/latest/features/panels/heatmap/) + """Generates Heatmap panel json structure (https://grafana.com/docs/grafana/latest/features/panels/heatmap/) :param heatmap :param cards: A heatmap card object: containing: From 548a77e85de863baf412da4c3144aa833d2ccde4 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Fri, 27 Mar 2020 17:09:28 -0400 Subject: [PATCH 074/403] More docstring tweaking --- grafanalib/core.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index eb0929f7..32c80671 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1954,9 +1954,7 @@ class Heatmap(object): """Generates Heatmap panel json structure (https://grafana.com/docs/grafana/latest/features/panels/heatmap/) :param heatmap - :param cards: A heatmap card object: containing: - "cardPadding" - "cardRound" + :param cards: A heatmap card object: keys "cardPadding", "cardRound" :param color: Heatmap color object :param dataFormat: 'timeseries' or 'tsbuckets' :param yBucketBound: 'auto', 'upper', 'middle', 'lower' From e871acbc6fe3031cb8e4194105ee5d695f127541 Mon Sep 17 00:00:00 2001 From: butlerx Date: Thu, 12 Sep 2019 14:15:49 +0100 Subject: [PATCH 075/403] add average metric aggregation for elastic search --- CHANGELOG.rst | 1 + grafanalib/elasticsearch.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 31832e8e..ab833a41 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,6 +21,7 @@ Changes * Add description attribute to Dashboard. * Add support for custom variables. * Point out documentation on readthedocs more clearly. +* Add average metric aggregation for elastic search 0.5.5 (2020-02-17) ================== diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 16eb43ab..20a58278 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -61,6 +61,21 @@ def to_json_data(self): } +@attr.s +class AverageMetricAgg(object): + """An aggregator that provides the average. value among the values. + + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html + + :param field: name of elasticsearch field to provide the maximum for + """ + + field = attr.ib(default="", validator=instance_of(str)) + + def to_json_data(self): + return {"type": "avg", "field": self.field, "settings": {}, "meta": {}} + + @attr.s class DateHistogramGroupBy(object): """A bucket aggregator that groups results by date. From 51463e091ad57ec57111861eab20c5dbb71ca8c4 Mon Sep 17 00:00:00 2001 From: jaychitalia95 Date: Tue, 31 Mar 2020 07:30:19 -0400 Subject: [PATCH 076/403] changed default to list for range and value maps --- CHANGELOG.rst | 1 + grafanalib/core.py | 34 +++------------------------------- 2 files changed, 4 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 044516ee..645ce86d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ Changes * Removed gfdatasource - feature is built in to Grafana since v5. * Generate API docs for readthedocs.org * Fix AlertList panel generation +* Added all parameters for StringColumnStyle 0.5.5 (2020-02-17) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index d301a7bd..921d3a43 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1352,32 +1352,7 @@ def to_json_data(self): 'valueName': self.valueName, 'timeFrom': self.timeFrom, } - -@attr.s -class TableRangeMaps(object): - fr = attr.ib(default = "") - to = attr.ib(default = "") - text = attr.ib(default = "", validator=instance_of(str)) - - def to_json_data(self): - return { - 'from': self.fr, - 'to': self.to, - 'text': self.text - } - - -@attr.s -class TableValueMaps(object): - value = attr.ib(default = "") - text = attr.ib(default = "", validator=instance_of(str)) - - def to_json_data(self): - return { - 'value': self.value, - 'text': self.text - } - + @attr.s class DateColumnStyleType(object): TYPE = 'date' @@ -1423,11 +1398,8 @@ class StringColumnStyleType(object): sanitize = attr.ib(validator=instance_of(bool), default = False) unit = attr.ib(default=SHORT_FORMAT) mappingType = attr.ib(default=MAPPING_TYPE_VALUE_TO_TEXT) - valueMaps = attr.ib(default = attr.Factory( - lambda: [TableValueMaps()]), validator=instance_of(list)) - rangeMaps = attr.ib(default = attr.Factory( - lambda: [TableRangeMaps()]), validator=instance_of(list)) - + valueMaps = attr.ib(default = attr.Factory(list)) + rangeMaps = attr.ib(default = attr.Factory(list)) def to_json_data(self): return { 'decimals' : self.decimals, From 55d19979581997244691f100ec25092d93f51449 Mon Sep 17 00:00:00 2001 From: jaychitalia95 Date: Tue, 31 Mar 2020 07:56:50 -0400 Subject: [PATCH 077/403] Style changes according to standards --- grafanalib/core.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 921d3a43..c2ed9797 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1352,7 +1352,8 @@ def to_json_data(self): 'valueName': self.valueName, 'timeFrom': self.timeFrom, } - + + @attr.s class DateColumnStyleType(object): TYPE = 'date' @@ -1390,26 +1391,27 @@ def to_json_data(self): @attr.s class StringColumnStyleType(object): TYPE = 'string' - decimals = attr.ib(default = 2, validator=instance_of(int)) + decimals = attr.ib(default=2, validator=instance_of(int)) colorMode = attr.ib(default=None) colors = attr.ib(default=attr.Factory(lambda: [GREEN, ORANGE, RED])) - thresholds = attr.ib(default = attr.Factory(list)) - preserveFormat = attr.ib(validator=instance_of(bool), default = False) - sanitize = attr.ib(validator=instance_of(bool), default = False) + thresholds = attr.ib(default=attr.Factory(list)) + preserveFormat = attr.ib(validator=instance_of(bool), default=False) + sanitize = attr.ib(validator=instance_of(bool), default=False) unit = attr.ib(default=SHORT_FORMAT) mappingType = attr.ib(default=MAPPING_TYPE_VALUE_TO_TEXT) - valueMaps = attr.ib(default = attr.Factory(list)) - rangeMaps = attr.ib(default = attr.Factory(list)) + valueMaps = attr.ib(default=attr.Factory(list)) + rangeMaps = attr.ib(default=attr.Factory(list)) + def to_json_data(self): return { - 'decimals' : self.decimals, - 'colorMode' : self.colorMode, - 'colors' : self.colors, - 'thresholds' : self.thresholds, - 'unit' : self.unit, - 'mappingType' : self.mappingType, - 'valueMaps' : self.valueMaps, - 'rangeMaps' : self.rangeMaps, + 'decimals': self.decimals, + 'colorMode': self.colorMode, + 'colors': self.colors, + 'thresholds': self.thresholds, + 'unit': self.unit, + 'mappingType': self.mappingType, + 'valueMaps': self.valueMaps, + 'rangeMaps': self.rangeMaps, 'preserveFormat': self.preserveFormat, 'sanitize': self.sanitize, 'type': self.TYPE, @@ -1431,8 +1433,9 @@ class ColumnStyle(object): alias = attr.ib(default="") pattern = attr.ib(default="") - align = attr.ib(default = "auto", validator=in_(["auto", "left", "right", "center"])) - link = attr.ib(validator=instance_of(bool), default = False) + align = attr.ib(default="auto", validator=in_( + ["auto", "left", "right", "center"])) + link = attr.ib(validator=instance_of(bool), default=False) linkOpenInNewTab = attr.ib(validator=instance_of(bool), default=False) linkUrl = attr.ib(validator=instance_of(str), default="") linkTooltip = attr.ib(validator=instance_of(str), default="") From f4757b22aebefec1f3dd31c3afdf4366da3916c5 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Tue, 31 Mar 2020 13:01:14 +0100 Subject: [PATCH 078/403] Bugfix for Elasticsearch TermsGroupBy ordering --- CHANGELOG.rst | 1 + grafanalib/elasticsearch.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 90a64cb8..daed8739 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,7 @@ Changes * Add support for custom variables. * Point out documentation on readthedocs more clearly. * Add average metric aggregation for elastic search +* Bugfix to query ordering in Elasticsearch TermsGroupBy 0.5.5 (2020-02-17) ================== diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 20a58278..4f3f62ca 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -178,7 +178,7 @@ def to_json_data(self): 'settings': { 'min_doc_count': self.minDocCount, 'order': self.order, - 'order_by': self.orderBy, + 'orderBy': self.orderBy, 'size': self.size, }, } From 134f493e145d4a5680a90be6ace8475ce9359a5a Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Mon, 6 Apr 2020 15:43:28 +0100 Subject: [PATCH 079/403] Added basic Statusmap panel support --- grafanalib/core.py | 139 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index e234f2b2..62304911 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -78,6 +78,7 @@ def to_json_data(self): BARGAUGE_TYPE = "bargauge" GAUGE_TYPE = "gauge" HEATMAP_TYPE = "heatmap" +STATUSMAP_TYPE = "flant-statusmap-panel" DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -2069,3 +2070,141 @@ def to_json_data(self): 'yBucketNumber': self.yBucketNumber, 'yBucketSize': self.yBucketSize } + + +@attr.s +class StatusmapColor(object): + """A Color object for Statusmaps + :param cardColor + :param colorScale + :param colorScheme + :param exponent + :param max + :param min + :param mode + """ + + # Maybe cardColor should validate to RGBA object, not sure + cardColor = attr.ib(default='#b4ff00', validator=instance_of(str)) + colorScale = attr.ib(default='sqrt', validator=instance_of(str)) + colorScheme = attr.ib(default='GnYlRd', validator=instance_of(str)) + exponent = attr.ib(default=0.5, validator=instance_of(float)) + mode = attr.ib(default="spectrum", validator=instance_of(str)) + thresholds = attr.ib(default=[], validator=instance_of(list)) + max = attr.ib(default=None) + min = attr.ib(default=None) + + def to_json_data(self): + return { + "mode": self.mode, + "cardColor": self.cardColor, + "colorScale": self.colorScale, + "exponent": self.exponent, + "colorScheme": self.colorScheme, + "max": self.max, + "min": self.min, + "thresholds": self.thresholds + } + + +@attr.s +class Statusmap(object): + """ + Generates json structure for the flant-statusmap-panel visualisation plugin: + https://grafana.com/grafana/plugins/flant-statusmap-panel/ + + :param alert: List of alerts to apply to the panel + :param cards: A statusmap card object: keys: "cardRound", "cardMinWidth", "cardHSpacing", "cardVSpacing" + :param color: A StatusmapColor object + :param dataSource: Name of the datasource to use + :param description: Description of the panel + :param editable + :param id + :param isNew + :param legend + :param links + :param minSpan + :param nullPointMode + :param span + :param targets + :param timeFrom + :param timeShift + :param title: Title of the panel + :param tooltip + :param transparent: Set panel transparency on/off + :param xAxis + :param yAxis + """ + targets = attr.ib() + title = attr.ib() + + alert = attr.ib(default=None) + cards = attr.ib( + default={ + "cardRound": None, + "cardMinWidth": 5, + "cardHSpacing": 2, + "cardVSpacing": 2, + }, validator=instance_of(dict)) + + color = attr.ib( + default=attr.Factory(StatusmapColor), + validator=instance_of(StatusmapColor), + ) + dataSource = attr.ib(default=None) + description = attr.ib(default=None) + editable = attr.ib(default=True, validator=instance_of(bool)) + id = attr.ib(default=None) + + isNew = attr.ib(default=True, validator=instance_of(bool)) + legend = attr.ib( + default=attr.Factory(Legend), + validator=instance_of(Legend), + ) + links = attr.ib(default=attr.Factory(list)) + minSpan = attr.ib(default=None) + nullPointMode = attr.ib(default=NULL_AS_ZERO) + span = attr.ib(default=None) + timeFrom = attr.ib(default=None) + timeShift = attr.ib(default=None) + tooltip = attr.ib( + default=attr.Factory(Tooltip), + validator=instance_of(Tooltip), + ) + transparent = attr.ib(default=False, validator=instance_of(bool)) + xAxis = attr.ib( + default=attr.Factory(XAxis), + validator=instance_of(XAxis) + ) + yAxis = attr.ib( + default=attr.Factory(YAxis), + validator=instance_of(YAxis) + ) + + + def to_json_data(self): + graphObject = { + 'datasource': self.dataSource, + 'description': self.description, + 'editable': self.editable, + 'color': self.color, + 'id': self.id, + 'isNew': self.isNew, + 'legend': self.legend, + 'links': self.links, + 'minSpan': self.minSpan, + 'nullPointMode': self.nullPointMode, + 'span': self.span, + 'targets': self.targets, + 'timeFrom': self.timeFrom, + 'timeShift': self.timeShift, + 'title': self.title, + 'tooltip': self.tooltip, + 'transparent': self.transparent, + 'type': STATUSMAP_TYPE, + 'xaxis': self.xAxis, + 'yaxis': self.yAxis, + } + if self.alert: + graphObject['alert'] = self.alert + return graphObject From 767712cd730f98ceaf509b0b529b2fe91b71104a Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Mon, 6 Apr 2020 15:49:18 +0100 Subject: [PATCH 080/403] Updated the changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52775a4b..cb0dc281 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -25,6 +25,7 @@ Changes * Add average metric aggregation for elastic search * Bugfix to query ordering in Elasticsearch TermsGroupBy * Added all parameters for StringColumnStyle +* Add ``Statusmap`` class (and ``StatusmapColor``) to support the Statusmap panel plugin 0.5.5 (2020-02-17) ================== From 53121f50c18519005982fa43f4fe27967600c7fe Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Mon, 6 Apr 2020 16:11:59 +0100 Subject: [PATCH 081/403] PEP8 fixes --- grafanalib/core.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 62304911..b84051ff 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2158,9 +2158,9 @@ class Statusmap(object): isNew = attr.ib(default=True, validator=instance_of(bool)) legend = attr.ib( - default=attr.Factory(Legend), - validator=instance_of(Legend), - ) + default=attr.Factory(Legend), + validator=instance_of(Legend), + ) links = attr.ib(default=attr.Factory(list)) minSpan = attr.ib(default=None) nullPointMode = attr.ib(default=NULL_AS_ZERO) @@ -2173,14 +2173,13 @@ class Statusmap(object): ) transparent = attr.ib(default=False, validator=instance_of(bool)) xAxis = attr.ib( - default=attr.Factory(XAxis), - validator=instance_of(XAxis) - ) + default=attr.Factory(XAxis), + validator=instance_of(XAxis) + ) yAxis = attr.ib( - default=attr.Factory(YAxis), - validator=instance_of(YAxis) - ) - + default=attr.Factory(YAxis), + validator=instance_of(YAxis) + ) def to_json_data(self): graphObject = { From d445e75212c9b36f23669fa60d86abe74adfb4e3 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Mon, 6 Apr 2020 16:21:48 +0100 Subject: [PATCH 082/403] Whitespace fix --- grafanalib/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index b84051ff..ec0ee292 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2135,6 +2135,7 @@ class Statusmap(object): :param xAxis :param yAxis """ + targets = attr.ib() title = attr.ib() From e04b6f75b06bf91456ff4e969b48c5d58a871e1a Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Mon, 6 Apr 2020 16:32:12 +0100 Subject: [PATCH 083/403] Whitespace updates --- grafanalib/core.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index ec0ee292..251ed3ea 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2075,6 +2075,7 @@ def to_json_data(self): @attr.s class StatusmapColor(object): """A Color object for Statusmaps + :param cardColor :param colorScale :param colorScheme @@ -2082,6 +2083,7 @@ class StatusmapColor(object): :param max :param min :param mode + :param thresholds """ # Maybe cardColor should validate to RGBA object, not sure @@ -2110,8 +2112,7 @@ def to_json_data(self): @attr.s class Statusmap(object): """ - Generates json structure for the flant-statusmap-panel visualisation plugin: - https://grafana.com/grafana/plugins/flant-statusmap-panel/ + Generates json structure for the flant-statusmap-panel visualisation plugin (https://grafana.com/grafana/plugins/flant-statusmap-panel/). :param alert: List of alerts to apply to the panel :param cards: A statusmap card object: keys: "cardRound", "cardMinWidth", "cardHSpacing", "cardVSpacing" From 9d4116b913c0e7cb55309031db6503521b116af9 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Mon, 6 Apr 2020 16:51:39 +0100 Subject: [PATCH 084/403] whitespace fix? --- grafanalib/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 251ed3ea..d461cd2b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2111,8 +2111,7 @@ def to_json_data(self): @attr.s class Statusmap(object): - """ - Generates json structure for the flant-statusmap-panel visualisation plugin (https://grafana.com/grafana/plugins/flant-statusmap-panel/). + """Generates json structure for the flant-statusmap-panel visualisation plugin (https://grafana.com/grafana/plugins/flant-statusmap-panel/). :param alert: List of alerts to apply to the panel :param cards: A statusmap card object: keys: "cardRound", "cardMinWidth", "cardHSpacing", "cardVSpacing" From 3c13a6c4db558cf5cf9cd49697ae8add43dec80f Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Mon, 6 Apr 2020 17:28:57 +0100 Subject: [PATCH 085/403] Sphinx docstring changes --- grafanalib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index d461cd2b..207da1c2 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2114,7 +2114,7 @@ class Statusmap(object): """Generates json structure for the flant-statusmap-panel visualisation plugin (https://grafana.com/grafana/plugins/flant-statusmap-panel/). :param alert: List of alerts to apply to the panel - :param cards: A statusmap card object: keys: "cardRound", "cardMinWidth", "cardHSpacing", "cardVSpacing" + :param cards: A statusmap card object: keys "cardRound", "cardMinWidth", "cardHSpacing", "cardVSpacing" :param color: A StatusmapColor object :param dataSource: Name of the datasource to use :param description: Description of the panel From ffebab2266cbbd47bdbcc1376c06dc633291b8ed Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Mon, 6 Apr 2020 18:14:48 +0100 Subject: [PATCH 086/403] Fixed Sphinx doc build tests --- grafanalib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 207da1c2..04b6eabe 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2113,7 +2113,7 @@ def to_json_data(self): class Statusmap(object): """Generates json structure for the flant-statusmap-panel visualisation plugin (https://grafana.com/grafana/plugins/flant-statusmap-panel/). - :param alert: List of alerts to apply to the panel + :param alert :param cards: A statusmap card object: keys "cardRound", "cardMinWidth", "cardHSpacing", "cardVSpacing" :param color: A StatusmapColor object :param dataSource: Name of the datasource to use From 9fae04e07d06367b12d509acb09047ffcfb13738 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Tue, 7 Apr 2020 15:10:50 +0100 Subject: [PATCH 087/403] Added hide functionality to Elasticsearch metrics --- grafanalib/elasticsearch.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 4f3f62ca..c020bae6 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -16,9 +16,13 @@ class CountMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html It's the default aggregator for elasticsearch queries. + :param hide: show/hide the metric in the final panel display """ + hide = attr.ib(default=False, validator=instance_of(bool)) + def to_json_data(self): return { + 'hide': self.hide, 'type': 'count', 'field': 'select field', 'settings': {}, @@ -32,11 +36,14 @@ class MaxMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-max-aggregation.html :param field: name of elasticsearch field to provide the maximum for + :param hide: show/hide the metric in the final panel display """ field = attr.ib(default="", validator=instance_of(str)) + hide = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): return { + 'hide': self.hide, 'type': 'max', 'field': self.field, 'settings': {}, @@ -50,11 +57,14 @@ class CardinalityMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-cardinality-aggregation.html :param field: name of elasticsearch field to provide the maximum for + :param hide: show/hide the metric in the final panel display """ field = attr.ib(default="", validator=instance_of(str)) + hide = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): return { + 'hide': self.hide, 'type': 'cardinality', 'field': self.field, 'settings': {}, @@ -68,12 +78,20 @@ class AverageMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html :param field: name of elasticsearch field to provide the maximum for + :param hide: show/hide the metric in the final panel display """ field = attr.ib(default="", validator=instance_of(str)) + hide = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): - return {"type": "avg", "field": self.field, "settings": {}, "meta": {}} + return { + "hide": self.hide, + "type": "avg", + "field": self.field, + "settings": {}, + "meta": {} + } @attr.s From d8ea802041f8302fcdacd9046097d6f13ad55a3f Mon Sep 17 00:00:00 2001 From: Duncan Walker Date: Tue, 7 Apr 2020 15:37:46 +0100 Subject: [PATCH 088/403] Update CHANGELOG.rst --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52775a4b..aa420714 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -25,6 +25,7 @@ Changes * Add average metric aggregation for elastic search * Bugfix to query ordering in Elasticsearch TermsGroupBy * Added all parameters for StringColumnStyle +* Added ability to hide metrics for Elasticsearch MetricAggs 0.5.5 (2020-02-17) ================== From b1ef3e1ee5b77c088d0f601c95ffd1c96492e3e0 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Tue, 7 Apr 2020 15:26:31 +0100 Subject: [PATCH 089/403] Added Elasticsearch derivative aggregator --- grafanalib/elasticsearch.py | 48 ++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 4f3f62ca..574c566c 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -16,9 +16,14 @@ class CountMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-valuecount-aggregation.html It's the default aggregator for elasticsearch queries. + + :param id: id of the metric """ + id = attr.ib(default=0, validator=instance_of(int)) + def to_json_data(self): return { + 'id': str(self.id), 'type': 'count', 'field': 'select field', 'settings': {}, @@ -32,11 +37,14 @@ class MaxMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-max-aggregation.html :param field: name of elasticsearch field to provide the maximum for + :param id: id of the metric """ field = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=0, validator=instance_of(int)) def to_json_data(self): return { + 'id': str(self.id), 'type': 'max', 'field': self.field, 'settings': {}, @@ -50,11 +58,14 @@ class CardinalityMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-cardinality-aggregation.html :param field: name of elasticsearch field to provide the maximum for + :param id: id of the metric """ field = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=0, validator=instance_of(int)) def to_json_data(self): return { + 'id': str(self.id), 'type': 'cardinality', 'field': self.field, 'settings': {}, @@ -68,12 +79,47 @@ class AverageMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html :param field: name of elasticsearch field to provide the maximum for + :param id: id of the metric """ field = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=0, validator=instance_of(int)) def to_json_data(self): - return {"type": "avg", "field": self.field, "settings": {}, "meta": {}} + return { + 'id': str(self.id), + "type": "avg", + "field": self.field, + "settings": {}, + "meta": {} + } + + +@attr.s +class DerivativeMetricAgg(object): + """An aggregator that takes the derivative of another metric aggregator. + + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-derivative-aggregation.html + + :param field: id of elasticsearch metric aggregator to provide the derivative of + :param hide: show/hide the metric in the final panel display + :param id: id of the metric + :param pipelineAgg: pipeline aggregator id + """ + field = attr.ib(default="", validator=instance_of(str)) + hide = attr.ib(default=False, validator=instance_of(bool)) + id = attr.ib(default=0, validator=instance_of(int)) + pipelineAgg = attr.ib(default=1, validator=instance_of(int)) + + def to_json_data(self): + return { + 'id': str(self.id), + 'pipelineAgg': str(self.pipelineAgg), + 'hide': self.hide, + 'type': 'derivative', + 'field': self.field, + 'settings': {}, + } @attr.s From fd226ad830348c716de5059eab9c128ba2ed4c12 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Tue, 7 Apr 2020 15:59:22 +0100 Subject: [PATCH 090/403] Update CHANGELOG.rst --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52775a4b..248f6b9e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -25,6 +25,7 @@ Changes * Add average metric aggregation for elastic search * Bugfix to query ordering in Elasticsearch TermsGroupBy * Added all parameters for StringColumnStyle +* Add derivative metric aggregation for Elasticsearch 0.5.5 (2020-02-17) ================== From e54b65eecd79891e82aafb0248f25fc698ebd173 Mon Sep 17 00:00:00 2001 From: Louis Ades Date: Mon, 13 Apr 2020 13:29:32 -0400 Subject: [PATCH 091/403] Add data links support to relevant grafanalib core panels --- grafanalib/core.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index e234f2b2..1568023d 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -539,6 +539,20 @@ def to_json_data(self): } +@attr.s +class DataLink(object): + title = attr.ib() + linkUrl = attr.ib(default="", validator=instance_of(str)) + isNewTab = attr.ib(default=False, validator=instance_of(bool)) + + def to_json_data(self): + return { + 'title': self.title, + 'url': self.linkUrl, + 'targetBlank': self.isNewTab, + } + + @attr.s class DataSourceInput(object): name = attr.ib() @@ -1012,6 +1026,7 @@ class Graph(object): """ Generates Graph panel json structure. + :param dataLinks: list of data links hooked to datapoints on the graph :param dataSource: DataSource's name :param minSpan: Minimum width for each panel :param repeat: Template's name to repeat Graph on @@ -1021,6 +1036,7 @@ class Graph(object): targets = attr.ib() aliasColors = attr.ib(default=attr.Factory(dict)) bars = attr.ib(default=False, validator=instance_of(bool)) + dataLinks = attr.ib(validator=instance_of(list)) dataSource = attr.ib(default=None) description = attr.ib(default=None) editable = attr.ib(default=True, validator=instance_of(bool)) @@ -1081,6 +1097,9 @@ def to_json_data(self): 'links': self.links, 'minSpan': self.minSpan, 'nullPointMode': self.nullPointMode, + 'options': { + 'dataLinks': self.dataLinks, + }, 'percentage': self.percentage, 'pointradius': self.pointRadius, 'points': self.points, @@ -1690,6 +1709,7 @@ class BarGauge(object): :param allValue: If All values should be shown or a Calculation :param cacheTimeout: metric query result cache ttl :param calc: Calculation to perform on metrics + :param dataLinks: list of data links hooked to datapoints on the graph :param dataSource: Grafana datasource name :param decimals: override automatic decimal precision for legend/tooltips :param description: optional panel description @@ -1727,6 +1747,7 @@ class BarGauge(object): allValues = attr.ib(default=False, validator=instance_of(bool)) cacheTimeout = attr.ib(default=None) calc = attr.ib(default=GAUGE_CALC_MEAN) + dataLinks = attr.ib(validator=instance_of(list)) dataSource = attr.ib(default=None) decimals = attr.ib(default=None) description = attr.ib(default=None) @@ -1799,6 +1820,7 @@ def to_json_data(self): "min": self.min, "title": self.label, "unit": self.format, + "links": self.dataLinks, }, "limit": self.limit, "mappings": self.valueMaps, @@ -1828,6 +1850,7 @@ class GaugePanel(object): :param allValue: If All values should be shown or a Calculation :param cacheTimeout: metric query result cache ttl :param calc: Calculation to perform on metrics + :param dataLinks: list of data links hooked to datapoints on the graph :param dataSource: Grafana datasource name :param decimals: override automatic decimal precision for legend/tooltips :param description: optional panel description @@ -1863,6 +1886,7 @@ class GaugePanel(object): allValues = attr.ib(default=False, validator=instance_of(bool)) cacheTimeout = attr.ib(default=None) calc = attr.ib(default=GAUGE_CALC_MEAN) + dataLinks = attr.ib(validator=instance_of(list)) dataSource = attr.ib(default=None) decimals = attr.ib(default=None) description = attr.ib(default=None) @@ -1920,6 +1944,7 @@ def to_json_data(self): "min": self.min, "title": self.label, "unit": self.format, + "links": self.dataLinks, }, "limit": self.limit, "mappings": self.valueMaps, From 0ff5d61dad161127cfcd386dd554274669e8c3b1 Mon Sep 17 00:00:00 2001 From: Louis Ades Date: Mon, 13 Apr 2020 13:38:05 -0400 Subject: [PATCH 092/403] Update changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52775a4b..c2aa4e4c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,6 +11,7 @@ Changes * Add ``Heatmap`` class (and ``HeatmapColor``) to support the Heatmap panel (#170) * Add ``BarGuage`` for creating bar guages panels in grafana 6 * Add ``GuagePanel`` for creating guages in grafana 6 +* Add data links support to ``Graph``, ``BarGuage``, and ``GuagePanel`` panels * Removed gfdatasource - feature is built in to Grafana since v5. * Generate API docs for readthedocs.org * Fix AlertList panel generation From d72af0deb8a443a993262eee533c4d051c20fb37 Mon Sep 17 00:00:00 2001 From: Louis Ades Date: Mon, 13 Apr 2020 14:04:11 -0400 Subject: [PATCH 093/403] Change validator to default --- grafanalib/core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 1568023d..d902cdfd 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1036,7 +1036,7 @@ class Graph(object): targets = attr.ib() aliasColors = attr.ib(default=attr.Factory(dict)) bars = attr.ib(default=False, validator=instance_of(bool)) - dataLinks = attr.ib(validator=instance_of(list)) + dataLinks = attr.ib(default=attr.Factory(list)) dataSource = attr.ib(default=None) description = attr.ib(default=None) editable = attr.ib(default=True, validator=instance_of(bool)) @@ -1747,7 +1747,7 @@ class BarGauge(object): allValues = attr.ib(default=False, validator=instance_of(bool)) cacheTimeout = attr.ib(default=None) calc = attr.ib(default=GAUGE_CALC_MEAN) - dataLinks = attr.ib(validator=instance_of(list)) + dataLinks = attr.ib(default=attr.Factory(list)) dataSource = attr.ib(default=None) decimals = attr.ib(default=None) description = attr.ib(default=None) @@ -1886,7 +1886,7 @@ class GaugePanel(object): allValues = attr.ib(default=False, validator=instance_of(bool)) cacheTimeout = attr.ib(default=None) calc = attr.ib(default=GAUGE_CALC_MEAN) - dataLinks = attr.ib(validator=instance_of(list)) + dataLinks = attr.ib(default=attr.Factory(list)) dataSource = attr.ib(default=None) decimals = attr.ib(default=None) description = attr.ib(default=None) From c87554df965d9853105f911d8041e0b98f6bc03e Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Wed, 15 Apr 2020 12:16:48 +0100 Subject: [PATCH 094/403] Added units for Elasticsearch derivative aggregator --- grafanalib/elasticsearch.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 574c566c..348cc426 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -105,20 +105,27 @@ class DerivativeMetricAgg(object): :param hide: show/hide the metric in the final panel display :param id: id of the metric :param pipelineAgg: pipeline aggregator id + :param unit: derivative units """ field = attr.ib(default="", validator=instance_of(str)) hide = attr.ib(default=False, validator=instance_of(bool)) id = attr.ib(default=0, validator=instance_of(int)) pipelineAgg = attr.ib(default=1, validator=instance_of(int)) + unit = attr.ib(default="", validator=instance_of(str)) + def to_json_data(self): + settings = {} + if self.unit != "": + settings["unit"] = self.unit + return { 'id': str(self.id), 'pipelineAgg': str(self.pipelineAgg), 'hide': self.hide, 'type': 'derivative', 'field': self.field, - 'settings': {}, + 'settings': settings, } From 763d4e62fe0ad56d5aea3ac7b2aea430d5044dd7 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Wed, 15 Apr 2020 12:34:43 +0100 Subject: [PATCH 095/403] Added Elasticsearch Sum Metric aggregation --- grafanalib/elasticsearch.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 4f3f62ca..635029d9 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -73,7 +73,36 @@ class AverageMetricAgg(object): field = attr.ib(default="", validator=instance_of(str)) def to_json_data(self): - return {"type": "avg", "field": self.field, "settings": {}, "meta": {}} + return { + 'id': str(self.id), + "hide": self.hide, + "type": "avg", + "field": self.field, + "settings": {}, + "meta": {} + } + +@attr.s +class SumMetricAgg(object): + """An aggregator that provides the sum of the values. + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html + :param field: name of elasticsearch field to provide the sum over + :param hide: show/hide the metric in the final panel display + :param id: id of the metric + """ + field = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=0, validator=instance_of(int)) + hide = attr.ib(default=False, validator=instance_of(bool)) + + def to_json_data(self): + return { + 'type': 'sum', + 'id': str(self.id), + 'hide': self.hide, + 'field': self.field, + 'settings': {}, + } + @attr.s From dd423c3e1e3b58e440633819dac2d0102fe8b903 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Wed, 15 Apr 2020 13:42:01 +0100 Subject: [PATCH 096/403] Whitespace fixes --- grafanalib/elasticsearch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 348cc426..10a2962e 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -113,7 +113,6 @@ class DerivativeMetricAgg(object): pipelineAgg = attr.ib(default=1, validator=instance_of(int)) unit = attr.ib(default="", validator=instance_of(str)) - def to_json_data(self): settings = {} if self.unit != "": From 5b0b5c2bb2b5345d397e8dce89ff7106faedb088 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Thu, 16 Apr 2020 17:27:44 +0100 Subject: [PATCH 097/403] Updated CHANGELOG.rst --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52775a4b..a25c7a35 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -25,6 +25,7 @@ Changes * Add average metric aggregation for elastic search * Bugfix to query ordering in Elasticsearch TermsGroupBy * Added all parameters for StringColumnStyle +* Add Elasticsearch Sum metric aggregator 0.5.5 (2020-02-17) ================== From 8cbe4d51e2374cfbe23ebb034d51fc6dc59176bf Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Thu, 16 Apr 2020 17:29:09 +0100 Subject: [PATCH 098/403] Whitespace updates --- grafanalib/elasticsearch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 635029d9..4e4f5bf7 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -17,6 +17,7 @@ class CountMetricAgg(object): It's the default aggregator for elasticsearch queries. """ + def to_json_data(self): return { 'type': 'count', @@ -82,6 +83,7 @@ def to_json_data(self): "meta": {} } + @attr.s class SumMetricAgg(object): """An aggregator that provides the sum of the values. @@ -104,7 +106,6 @@ def to_json_data(self): } - @attr.s class DateHistogramGroupBy(object): """A bucket aggregator that groups results by date. From a2ad423a8b04f610c25b40f515a9825f002e7ab8 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Wed, 29 Apr 2020 15:52:03 +0200 Subject: [PATCH 099/403] Update to new junit_family Thanks Oriol Pauleria for the fix https://github.com/weaveworks/grafanalib/pull/184/files#r349836896 closes: #223 --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 73b55cb9..4e719340 100644 --- a/tox.ini +++ b/tox.ini @@ -7,7 +7,7 @@ envlist = py35, py37, py38 [testenv] -commands = pytest --junitxml=test-results/junit-{envname}.xml +commands = pytest -o junit_family=xunit2 --junitxml=test-results/junit-{envname}.xml deps = pytest From ccb511077cc8255894e7486d44ee4501c0cc0076 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Wed, 29 Apr 2020 15:58:20 +0200 Subject: [PATCH 100/403] Use attr.evolve instead of attr.assoc Apparently attr.assoc has been replaced by attr.evolve since a long time ago: https://www.attrs.org/en/stable/api.html#attr.assoc closes: #224 --- grafanalib/elasticsearch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 4f3f62ca..cfb6b1c1 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -209,7 +209,7 @@ class ElasticsearchTarget(object): refId = attr.ib(default="", validator=instance_of(str)) def _map_bucket_aggs(self, f): - return attr.assoc(self, bucketAggs=list(map(f, self.bucketAggs))) + return attr.evolve(self, bucketAggs=list(map(f, self.bucketAggs))) def auto_bucket_agg_ids(self): """Give unique IDs all bucketAggs without ID. From c409fc6b559559a003f201b26ca1123f86a94301 Mon Sep 17 00:00:00 2001 From: Andrew D'Amico Date: Wed, 29 Apr 2020 15:11:08 +0000 Subject: [PATCH 101/403] Updated gauge default threshold values. --- CHANGELOG.rst | 1 + grafanalib/core.py | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 52775a4b..b56ee13d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -25,6 +25,7 @@ Changes * Add average metric aggregation for elastic search * Bugfix to query ordering in Elasticsearch TermsGroupBy * Added all parameters for StringColumnStyle +* Bugfix to update default ``Threshold`` values for ``GaugePanel`` and ``BarGauge`` 0.5.5 (2020-02-17) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index e234f2b2..246a0d73 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1765,8 +1765,8 @@ class BarGauge(object): thresholds = attr.ib( default=attr.Factory( lambda: [ - Threshold("green", 0, 0), - Threshold("red", 1, 80) + Threshold("green", 0, 0.0), + Threshold("red", 1, 80.0) ] ), validator=instance_of(list), @@ -1887,8 +1887,8 @@ class GaugePanel(object): thresholds = attr.ib( default=attr.Factory( lambda: [ - Threshold("green", 0, 0), - Threshold("red", 1, 80) + Threshold("green", 0, 0.0), + Threshold("red", 1, 80.0) ] ), validator=instance_of(list), From b46110f8d84dab188d3dd05981256874cff8abd9 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Wed, 29 Apr 2020 16:29:37 +0200 Subject: [PATCH 102/403] use GH actions for tests - run tests on various python versions - build docs - check links --- .circleci/config.yml | 29 ----------------- .github/workflows/check-sphinx-and-links.yml | 34 ++++++++++++++++++++ .github/workflows/run-tests.yml | 22 +++++++++++++ Dockerfile.docs | 15 --------- Makefile | 13 -------- docs/releasing.rst | 6 ++-- docs/requirements.txt | 3 +- grafanalib/core.py | 2 +- 8 files changed, 62 insertions(+), 62 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/check-sphinx-and-links.yml create mode 100644 .github/workflows/run-tests.yml delete mode 100644 Dockerfile.docs diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 11c91596..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,29 +0,0 @@ -version: 2 - -jobs: - build: - docker: - - image: circleci/python:3.8.2 - steps: - - checkout - - setup_remote_docker - - run: - name: Dependencies - command: | - git clone --depth 1 -b v1.2.17 https://github.com/pyenv/pyenv.git $HOME/.pyenv - $HOME/.pyenv/bin/pyenv install 3.5.8 - $HOME/.pyenv/bin/pyenv install 3.7.5 - $HOME/.pyenv/bin/pyenv install 3.8.2 - pip3 install --user tox flake8 - pip3 install --user -r docs/requirements.txt - make deps - - run: - name: make all - command: | - make all - # Test that the documentation is okay - - run: make test-docs - - store_test_results: - path: test-results - - store_artifacts: - path: test-results diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml new file mode 100644 index 00000000..a89b9e43 --- /dev/null +++ b/.github/workflows/check-sphinx-and-links.yml @@ -0,0 +1,34 @@ +name: "Check docs and links" +on: +- pull_request +- push + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + + - name: "Install prerequisites" + run: | + pip3 install -r docs/requirements.txt + + - uses: ammaraskar/sphinx-action@master + with: + docs-folder: "docs/" + pre-build-command: | + sphinx-apidoc -f grafanalib -o docs/api + python setup.py install --user + build-command: "make html" + + - name: Link Checker + id: lc + uses: peter-evans/link-checker@v1 + with: + args: -x 'github.com/weaveworks/grafanalib/(issue|pull)' -r docs/build/html/* + - name: Fail if there were link errors + run: exit ${{ steps.lc.outputs.exit_code }} diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 00000000..6e093115 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,22 @@ +name: Run tests + +on: [push, pull_request] + +jobs: + build-n-publish: + name: Run tests + runs-on: ubuntu-18.04 + strategy: + matrix: + python: [3.5, 3.7, 3.8] + steps: + - uses: actions/checkout@master + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python }} + - name: Run tests + run: | + pip3 install tox flake8 + make deps + make all diff --git a/Dockerfile.docs b/Dockerfile.docs deleted file mode 100644 index 996000b3..00000000 --- a/Dockerfile.docs +++ /dev/null @@ -1,15 +0,0 @@ -FROM ddidier/sphinx-doc - -WORKDIR /doc - -COPY . . - -RUN pip3 install -r docs/requirements.txt && \ - apt update && apt install -y linkchecker && \ - python3 setup.py install && \ - cd docs && mkdir _static && make html && \ - cd build/html/_static/fonts/RobotoSlab && \ - ln -s roboto-slab-v7-regular.eot roboto-slab.eot - - -CMD ["python", "-m", "http.server", "8000"] diff --git a/Makefile b/Makefile index 7a1284e3..dfbdbda5 100644 --- a/Makefile +++ b/Makefile @@ -78,16 +78,3 @@ clean: clean-deps: rm -rf $(VIRTUALENV_DIR) - -update-docs-modules: - sphinx-apidoc -f grafanalib -o docs/api - -build-docs: update-docs-modules - docker build -t grafanalib-docs -f Dockerfile.docs . - -test-docs: build-docs - @docker run -it grafanalib-docs /usr/bin/linkchecker docs/build/html/index.html - -serve-docs: build-docs - @echo Starting docs website on http://localhost:${DOCS_PORT}/docs/build/html/index.html - @docker run -i -p ${DOCS_PORT}:8000 -e USER_ID=$$UID grafanalib-docs diff --git a/docs/releasing.rst b/docs/releasing.rst index 2103096f..ea7fa4f3 100644 --- a/docs/releasing.rst +++ b/docs/releasing.rst @@ -6,8 +6,8 @@ Pre-release ----------- * Pick a new version number (e.g. ``X.Y.Z``) -* Update `CHANGELOG <../CHANGELOG.rst>`_ with that number -* Update `setup.py <../setup.py>`_ with that number +* Update `CHANGELOG `_ with that number +* Update `setup.py `_ with that number Smoke-testing ------------- @@ -19,7 +19,7 @@ Smoke-testing $ python setup.py install * Check ``~/.local/bin/generate-dashboard`` for the update version. -* Try the example on `README <../README.rst>`_. +* Try the example on `README `_. Releasing --------- diff --git a/docs/requirements.txt b/docs/requirements.txt index 06829942..41a40666 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,2 @@ -sphinx == 2.4.1 +sphinx == 3.0.3 +sphinx_rtd_theme == 0.4.3 diff --git a/grafanalib/core.py b/grafanalib/core.py index e234f2b2..ff57e9e3 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1550,7 +1550,7 @@ def _style_columns(columns): class Table(object): """Generates Table panel json structure - Grafana doc on table: http://docs.grafana.org/reference/table_panel/ + Grafana doc on table: https://grafana.com/docs/grafana/latest/features/panels/table_panel/#table-panel :param columns: table columns for Aggregations view :param dataSource: Grafana datasource name From fe8425f6eed683426afe3439d21152a577a4f9c2 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 08:28:54 +0200 Subject: [PATCH 103/403] update CHANGELOG.rst --- CHANGELOG.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 099fa941..62fcda30 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,7 @@ Changelog ========= -Next release +0.5.6 (TBD) ============ Changes @@ -27,6 +27,10 @@ Changes * Added all parameters for StringColumnStyle * Add ``Statusmap`` class (and ``StatusmapColor``) to support the Statusmap panel plugin * Bugfix to update default ``Threshold`` values for ``GaugePanel`` and ``BarGauge`` +* Use Github Actions for CI. +* Fix test warnings. +* Update ``BarGauge`` and ``GaugePanel`` default Threshold values. +* Update release instructions. 0.5.5 (2020-02-17) ================== From de23b0a2a178cd9e39020025ec26f6064c618a24 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 08:31:43 +0200 Subject: [PATCH 104/403] add committers --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 62fcda30..3a627d37 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -32,6 +32,8 @@ Changes * Update ``BarGauge`` and ``GaugePanel`` default Threshold values. * Update release instructions. +Thanks a lot to the contributions from @DWalker487, @bboreham, @butlerx, @dholbach, @franzs, @jaychitalia95, @matthewmrichter and @number492 for this release! + 0.5.5 (2020-02-17) ================== From cbc8ae640508f2a2ef64bd53f5a50ae304be5146 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 08:42:04 +0200 Subject: [PATCH 105/403] stop checking http://sphinx-doc.org/ - the link is part of the theme and it times out right now --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index a89b9e43..59d5491d 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -29,6 +29,6 @@ jobs: id: lc uses: peter-evans/link-checker@v1 with: - args: -x 'github.com/weaveworks/grafanalib/(issue|pull)' -r docs/build/html/* + args: -x 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' -r docs/build/html/* - name: Fail if there were link errors run: exit ${{ steps.lc.outputs.exit_code }} From d100719b23087233f4170599a9fcc06e8799773e Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 10:44:35 +0200 Subject: [PATCH 106/403] prepare 0.5.6 release --- CHANGELOG.rst | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c131ce22..56039c8d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog ========= -0.5.6 (TBD) -============ +0.5.6 (2020-05-05) +================== Changes ------- diff --git a/setup.py b/setup.py index 162b6c81..f20c2531 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.5', + version='0.5.6', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 62ade22d77741ce6d062b66cd004c19262256a76 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 11:56:40 +0200 Subject: [PATCH 107/403] drop circle ci badge --- README.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.rst b/README.rst index e0d030a4..c8eded9e 100644 --- a/README.rst +++ b/README.rst @@ -2,9 +2,6 @@ Getting Started with grafanalib =============================== -.. image:: https://circleci.com/gh/weaveworks/grafanalib.svg?style=shield - :target: https://circleci.com/gh/weaveworks/grafanalib - .. image:: https://readthedocs.org/projects/grafanalib/badge/?version=latest :alt: Documentation Status :scale: 100% From f66474a9ae902f859a77f68f7df896db320dc8d0 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 11:57:21 +0200 Subject: [PATCH 108/403] update to tested versions of python --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f20c2531..cddc2576 100644 --- a/setup.py +++ b/setup.py @@ -33,8 +33,9 @@ def local_file(name): 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: System :: Monitoring', ], install_requires=[ From e6a64e91574229b759c2e7efea59ddb8f4971873 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 15:27:42 +0200 Subject: [PATCH 109/403] remove another Circle CI mention --- docs/getting-started.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 6ff6c44a..759af6d2 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -2,9 +2,6 @@ Getting Started with grafanalib =============================== -.. image:: https://circleci.com/gh/weaveworks/grafanalib.svg?style=shield - :target: https://circleci.com/gh/weaveworks/grafanalib - Do you like `Grafana `_ but wish you could version your dashboard configuration? Do you find yourself repeating common patterns? If so, grafanalib is for you. From 4ac85344e8a73f7023a938290976b459fb6cc003 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 16:35:11 +0200 Subject: [PATCH 110/403] open 0.5.7 for development --- CHANGELOG.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 56039c8d..1fc05747 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog ========= +0.5.7 (TBD) +=========== + +Changes +------- + +* <...> + 0.5.6 (2020-05-05) ================== From 0474c8628e2e1732d75022f1a88458856470cfe8 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 16:28:20 +0200 Subject: [PATCH 111/403] remove tools directory, we're not interacting with any form of image any more --- Makefile | 31 +- tools/.gitignore | 10 - tools/README.md | 52 --- tools/build/Makefile | 46 --- tools/build/golang/Dockerfile | 49 --- tools/build/golang/build.sh | 22 -- tools/build/haskell/Dockerfile | 4 - tools/build/haskell/build.sh | 12 - tools/build/haskell/copy-libraries | 41 -- tools/circle.yml | 55 --- tools/config_management/README.md | 141 ------- tools/config_management/group_vars/all | 11 - .../library/setup_ansible_dependencies.yml | 33 -- .../dev-tools/files/apt-daily.timer.conf | 2 - .../roles/dev-tools/tasks/main.yml | 48 --- .../docker-configuration/files/docker.conf | 3 - .../roles/docker-configuration/tasks/main.yml | 36 -- .../tasks/debian.yml | 35 -- .../docker-from-docker-ce-repo/tasks/main.yml | 10 - .../tasks/redhat.yml | 29 -- .../docker-from-docker-repo/tasks/debian.yml | 35 -- .../docker-from-docker-repo/tasks/main.yml | 10 - .../docker-from-docker-repo/tasks/redhat.yml | 25 -- .../tasks/debian.yml | 8 - .../docker-from-get.docker.com/tasks/main.yml | 10 - .../tasks/redhat.yml | 11 - .../roles/docker-from-tarball/tasks/main.yml | 61 --- .../roles/docker-from-tarball/vars/main.yml | 5 - .../roles/docker-install/tasks/main.yml | 30 -- .../docker-prerequisites/tasks/debian.yml | 11 - .../roles/docker-prerequisites/tasks/main.yml | 5 - .../roles/golang-from-tarball/tasks/main.yml | 36 -- .../roles/kubelet-stop/tasks/main.yml | 14 - .../kubernetes-docker-images/tasks/main.yml | 14 - .../roles/kubernetes-install/tasks/debian.yml | 37 -- .../roles/kubernetes-install/tasks/main.yml | 16 - .../roles/kubernetes-install/tasks/redhat.yml | 30 -- .../roles/kubernetes-start/tasks/main.yml | 42 -- .../roles/setup-ansible/pre_tasks/main.yml | 26 -- .../roles/sock-shop/tasks/tasks.yml | 34 -- .../roles/weave-kube/tasks/main.yml | 24 -- .../roles/weave-net-sources/tasks/main.yml | 24 -- .../roles/weave-net-utilities/tasks/main.yml | 56 --- .../roles/weave-net/tasks/main.yml | 26 -- tools/config_management/setup_weave-kube.yml | 27 -- .../setup_weave-net_debug.yml | 18 - .../config_management/setup_weave-net_dev.yml | 20 - .../setup_weave-net_test.yml | 20 - tools/cover/Makefile | 11 - tools/cover/cover.go | 97 ----- tools/cover/gather_coverage.sh | 20 - tools/dependencies/cross_versions.py | 91 ----- tools/dependencies/list_os_images.sh | 85 ----- tools/dependencies/list_versions.py | 343 ----------------- tools/files-with-type | 11 - tools/image-tag | 9 - tools/integration/assert.sh | 193 ---------- tools/integration/config.sh | 130 ------- tools/integration/gce.sh | 202 ---------- tools/integration/run_all.sh | 31 -- tools/integration/sanity_check.sh | 26 -- tools/lint | 255 ------------- tools/provisioning/README.md | 55 --- tools/provisioning/aws/README.md | 90 ----- tools/provisioning/aws/main.tf | 137 ------- tools/provisioning/aws/outputs.tf | 54 --- tools/provisioning/aws/variables.tf | 68 ---- tools/provisioning/do/README.md | 98 ----- tools/provisioning/do/main.tf | 42 -- tools/provisioning/do/outputs.tf | 57 --- tools/provisioning/do/variables.tf | 185 --------- tools/provisioning/gcp/README.md | 126 ------ tools/provisioning/gcp/main.tf | 79 ---- tools/provisioning/gcp/outputs.tf | 66 ---- tools/provisioning/gcp/variables.tf | 77 ---- tools/provisioning/setup.sh | 361 ------------------ tools/publish-site | 50 --- tools/push-images | 53 --- tools/rebuild-image | 79 ---- tools/runner/Makefile | 11 - tools/runner/runner.go | 290 -------------- tools/sched | 40 -- tools/scheduler/.gitignore | 1 - tools/scheduler/README.md | 6 - tools/scheduler/app.yaml | 15 - tools/scheduler/appengine_config.py | 3 - tools/scheduler/cron.yaml | 4 - tools/scheduler/main.py | 206 ---------- tools/scheduler/requirements.txt | 2 - tools/shell-lint | 13 - tools/socks/Dockerfile | 7 - tools/socks/Makefile | 29 -- tools/socks/README.md | 53 --- tools/socks/connect.sh | 26 -- tools/socks/main.go | 97 ----- tools/test | 154 -------- 96 files changed, 1 insertion(+), 5482 deletions(-) delete mode 100644 tools/.gitignore delete mode 100644 tools/README.md delete mode 100644 tools/build/Makefile delete mode 100644 tools/build/golang/Dockerfile delete mode 100755 tools/build/golang/build.sh delete mode 100644 tools/build/haskell/Dockerfile delete mode 100755 tools/build/haskell/build.sh delete mode 100755 tools/build/haskell/copy-libraries delete mode 100644 tools/circle.yml delete mode 100644 tools/config_management/README.md delete mode 100644 tools/config_management/group_vars/all delete mode 100644 tools/config_management/library/setup_ansible_dependencies.yml delete mode 100644 tools/config_management/roles/dev-tools/files/apt-daily.timer.conf delete mode 100644 tools/config_management/roles/dev-tools/tasks/main.yml delete mode 100644 tools/config_management/roles/docker-configuration/files/docker.conf delete mode 100644 tools/config_management/roles/docker-configuration/tasks/main.yml delete mode 100644 tools/config_management/roles/docker-from-docker-ce-repo/tasks/debian.yml delete mode 100644 tools/config_management/roles/docker-from-docker-ce-repo/tasks/main.yml delete mode 100644 tools/config_management/roles/docker-from-docker-ce-repo/tasks/redhat.yml delete mode 100644 tools/config_management/roles/docker-from-docker-repo/tasks/debian.yml delete mode 100644 tools/config_management/roles/docker-from-docker-repo/tasks/main.yml delete mode 100644 tools/config_management/roles/docker-from-docker-repo/tasks/redhat.yml delete mode 100644 tools/config_management/roles/docker-from-get.docker.com/tasks/debian.yml delete mode 100644 tools/config_management/roles/docker-from-get.docker.com/tasks/main.yml delete mode 100644 tools/config_management/roles/docker-from-get.docker.com/tasks/redhat.yml delete mode 100644 tools/config_management/roles/docker-from-tarball/tasks/main.yml delete mode 100644 tools/config_management/roles/docker-from-tarball/vars/main.yml delete mode 100644 tools/config_management/roles/docker-install/tasks/main.yml delete mode 100644 tools/config_management/roles/docker-prerequisites/tasks/debian.yml delete mode 100644 tools/config_management/roles/docker-prerequisites/tasks/main.yml delete mode 100644 tools/config_management/roles/golang-from-tarball/tasks/main.yml delete mode 100644 tools/config_management/roles/kubelet-stop/tasks/main.yml delete mode 100644 tools/config_management/roles/kubernetes-docker-images/tasks/main.yml delete mode 100644 tools/config_management/roles/kubernetes-install/tasks/debian.yml delete mode 100644 tools/config_management/roles/kubernetes-install/tasks/main.yml delete mode 100644 tools/config_management/roles/kubernetes-install/tasks/redhat.yml delete mode 100644 tools/config_management/roles/kubernetes-start/tasks/main.yml delete mode 100644 tools/config_management/roles/setup-ansible/pre_tasks/main.yml delete mode 100644 tools/config_management/roles/sock-shop/tasks/tasks.yml delete mode 100644 tools/config_management/roles/weave-kube/tasks/main.yml delete mode 100644 tools/config_management/roles/weave-net-sources/tasks/main.yml delete mode 100644 tools/config_management/roles/weave-net-utilities/tasks/main.yml delete mode 100644 tools/config_management/roles/weave-net/tasks/main.yml delete mode 100644 tools/config_management/setup_weave-kube.yml delete mode 100644 tools/config_management/setup_weave-net_debug.yml delete mode 100644 tools/config_management/setup_weave-net_dev.yml delete mode 100644 tools/config_management/setup_weave-net_test.yml delete mode 100644 tools/cover/Makefile delete mode 100644 tools/cover/cover.go delete mode 100755 tools/cover/gather_coverage.sh delete mode 100755 tools/dependencies/cross_versions.py delete mode 100755 tools/dependencies/list_os_images.sh delete mode 100755 tools/dependencies/list_versions.py delete mode 100755 tools/files-with-type delete mode 100755 tools/image-tag delete mode 100755 tools/integration/assert.sh delete mode 100644 tools/integration/config.sh delete mode 100755 tools/integration/gce.sh delete mode 100755 tools/integration/run_all.sh delete mode 100755 tools/integration/sanity_check.sh delete mode 100755 tools/lint delete mode 100755 tools/provisioning/README.md delete mode 100644 tools/provisioning/aws/README.md delete mode 100755 tools/provisioning/aws/main.tf delete mode 100755 tools/provisioning/aws/outputs.tf delete mode 100755 tools/provisioning/aws/variables.tf delete mode 100755 tools/provisioning/do/README.md delete mode 100755 tools/provisioning/do/main.tf delete mode 100755 tools/provisioning/do/outputs.tf delete mode 100755 tools/provisioning/do/variables.tf delete mode 100755 tools/provisioning/gcp/README.md delete mode 100755 tools/provisioning/gcp/main.tf delete mode 100755 tools/provisioning/gcp/outputs.tf delete mode 100755 tools/provisioning/gcp/variables.tf delete mode 100755 tools/provisioning/setup.sh delete mode 100755 tools/publish-site delete mode 100755 tools/push-images delete mode 100755 tools/rebuild-image delete mode 100644 tools/runner/Makefile delete mode 100644 tools/runner/runner.go delete mode 100755 tools/sched delete mode 100644 tools/scheduler/.gitignore delete mode 100644 tools/scheduler/README.md delete mode 100644 tools/scheduler/app.yaml delete mode 100644 tools/scheduler/appengine_config.py delete mode 100644 tools/scheduler/cron.yaml delete mode 100644 tools/scheduler/main.py delete mode 100644 tools/scheduler/requirements.txt delete mode 100755 tools/shell-lint delete mode 100644 tools/socks/Dockerfile delete mode 100644 tools/socks/Makefile delete mode 100644 tools/socks/README.md delete mode 100755 tools/socks/connect.sh delete mode 100644 tools/socks/main.go delete mode 100755 tools/test diff --git a/Makefile b/Makefile index dfbdbda5..8a9ce2ee 100644 --- a/Makefile +++ b/Makefile @@ -1,35 +1,11 @@ .PHONY: all clean clean-deps lint test deps coverage .DEFAULT_GOAL := all -# Boiler plate for bulding Docker containers. -# All this must go at top of file I'm afraid. -IMAGE_PREFIX := weaveworks -IMAGE_TAG := $(shell ./tools/image-tag) -GIT_REVISION := $(shell git rev-parse HEAD) -UPTODATE := .uptodate - -# Building Docker images is now automated. The convention is every directory -# with a Dockerfile in it builds an image calls weaveworks/. -# Dependencies (i.e. things that go in the image) still need to be explicitly -# declared. -%/$(UPTODATE): %/Dockerfile - $(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -t $(IMAGE_PREFIX)/$(shell basename $(@D)) $(@D)/ - $(SUDO) docker tag $(IMAGE_PREFIX)/$(shell basename $(@D)) $(IMAGE_PREFIX)/$(shell basename $(@D)):$(IMAGE_TAG) - touch $@ - -# Get a list of directories containing Dockerfiles -DOCKERFILES=$(shell find * -type f -name Dockerfile ! -path "tools/*" ! -path "vendor/*") -UPTODATE_FILES=$(patsubst %/Dockerfile,%/$(UPTODATE),$(DOCKERFILES)) -DOCKER_IMAGE_DIRS=$(patsubst %/Dockerfile,%,$(DOCKERFILES)) -IMAGE_NAMES=$(foreach dir,$(DOCKER_IMAGE_DIRS),$(patsubst %,$(IMAGE_PREFIX)/%,$(shell basename $(dir)))) - # Python-specific stuff TOX := $(shell command -v tox 2> /dev/null) PIP := $(shell command -v pip3 2> /dev/null) FLAKE8 := $(shell command -v flake8 2> /dev/null) -DOCS_PORT:=8000 - .ensure-tox: .ensure-pip ifndef TOX rm -f .ensure-tox @@ -51,10 +27,7 @@ ifndef FLAKE8 endif touch .ensure-pip -images: - $(info $(IMAGE_NAMES)) - -all: $(UPTODATE_FILES) test lint coverage +all: test lint coverage deps: setup.py .ensure-tox tox.ini @@ -70,8 +43,6 @@ coverage: $(TOX) -e coverage clean: - $(SUDO) docker rmi $(IMAGE_NAMES) >/dev/null 2>&1 || true - rm -rf $(UPTODATE_FILES) rm -rf grafanalib.egg-info rm -f .ensure-pip .ensure-tox .ensure-flake8 find . -name '*.pyc' | xargs rm diff --git a/tools/.gitignore b/tools/.gitignore deleted file mode 100644 index 308ae9d3..00000000 --- a/tools/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -cover/cover -socks/proxy -socks/image.tar -runner/runner -*.pyc -*~ -terraform.tfstate -terraform.tfstate.backup -*.retry -build/**/.uptodate diff --git a/tools/README.md b/tools/README.md deleted file mode 100644 index 9092b8e2..00000000 --- a/tools/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Weaveworks Build Tools - -Included in this repo are tools shared by weave.git and scope.git. They include - -- ```build```: a set of docker base-images for building weave - projects. These should be used instead of giving each project its - own build image. -- ```provisioning```: a set of Terraform scripts to provision virtual machines in GCP, AWS or Digital Ocean. -- ```config_management```: a set of Ansible playbooks to configure virtual machines for development, testing, etc. -- ```cover```: a tool which merges overlapping coverage reports generated by go - test -- ```files-with-type```: a tool to search directories for files of a given - MIME type -- ```lint```: a script to lint go, sh and hcl files; runs various tools like - golint, go vet, errcheck, shellcheck etc -- ```rebuild-image```: a script to rebuild docker images when their input files - change; useful when you using docker images to build your software, but you - don't want to build the image every time. -- ```shell-lint```: a script to lint multiple shell files with - [shellcheck](http://www.shellcheck.net/) -- ```socks```: a simple, dockerised SOCKS proxy for getting your laptop onto - the Weave network -- ```test```: a script to run all go unit tests in subdirectories, gather the - coverage results, and merge them into a single report. -- ```runner```: a tool for running tests in parallel; given each test is - suffixed with the number of hosts it requires, and the hosts available are - contained in the environment variable HOSTS, the tool will run tests in - parallel, on different hosts. -- ```scheduler```: an appengine application that can be used to distribute - tests across different shards in CircleCI. - -## Requirements - -- ```lint``` requires shfmt to lint sh files; get shfmt with - ```go get -u gopkg.in/mvdan/sh.v1/cmd/shfmt``` - -## Using build-tools.git - -To allow you to tie your code to a specific version of build-tools.git, such -that future changes don't break you, we recommendation that you [`git subtree`]() -this repository into your own repository: - -[`git subtree`]: http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/ - -``` -git subtree add --prefix tools https://github.com/weaveworks/build-tools.git master --squash -```` - -To update the code in build-tools.git, the process is therefore: -- PR into build-tools.git, go through normal review process etc. -- Do `git subtree pull --prefix tools https://github.com/weaveworks/build-tools.git master --squash` - in your repo, and PR that. diff --git a/tools/build/Makefile b/tools/build/Makefile deleted file mode 100644 index cea049be..00000000 --- a/tools/build/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -.PHONY: all clean images -.DEFAULT_GOAL := all - -# Boiler plate for bulding Docker containers. -# All this must go at top of file I'm afraid. -IMAGE_PREFIX := quay.io/weaveworks/build- -IMAGE_TAG := $(shell ../image-tag) -UPTODATE := .uptodate - -# Every directory with a Dockerfile in it builds an image called -# $(IMAGE_PREFIX). Dependencies (i.e. things that go in the image) -# still need to be explicitly declared. -%/$(UPTODATE): %/Dockerfile %/* - $(SUDO) docker build -t $(IMAGE_PREFIX)$(shell basename $(@D)) $(@D)/ - $(SUDO) docker tag $(IMAGE_PREFIX)$(shell basename $(@D)) $(IMAGE_PREFIX)$(shell basename $(@D)):$(IMAGE_TAG) - touch $@ - -# Get a list of directories containing Dockerfiles -DOCKERFILES := $(shell find . -name tools -prune -o -name vendor -prune -o -type f -name 'Dockerfile' -print) -UPTODATE_FILES := $(patsubst %/Dockerfile,%/$(UPTODATE),$(DOCKERFILES)) -DOCKER_IMAGE_DIRS := $(patsubst %/Dockerfile,%,$(DOCKERFILES)) -IMAGE_NAMES := $(foreach dir,$(DOCKER_IMAGE_DIRS),$(patsubst %,$(IMAGE_PREFIX)%,$(shell basename $(dir)))) -images: - $(info $(IMAGE_NAMES)) - @echo > /dev/null - -# Define imagetag-golang, etc, for each image, which parses the dockerfile and -# prints an image tag. For example: -# FROM golang:1.8.1-stretch -# in the "foo/Dockerfile" becomes: -# $ make imagetag-foo -# 1.8.1-stretch -define imagetag_dep -.PHONY: imagetag-$(1) -$(patsubst $(IMAGE_PREFIX)%,imagetag-%,$(1)): $(patsubst $(IMAGE_PREFIX)%,%,$(1))/Dockerfile - @cat $$< | grep "^FROM " | head -n1 | sed 's/FROM \(.*\):\(.*\)/\2/' -endef -$(foreach image, $(IMAGE_NAMES), $(eval $(call imagetag_dep, $(image)))) - -all: $(UPTODATE_FILES) - -clean: - $(SUDO) docker rmi $(IMAGE_NAMES) >/dev/null 2>&1 || true - rm -rf $(UPTODATE_FILES) - - diff --git a/tools/build/golang/Dockerfile b/tools/build/golang/Dockerfile deleted file mode 100644 index 8ef1d2b0..00000000 --- a/tools/build/golang/Dockerfile +++ /dev/null @@ -1,49 +0,0 @@ -FROM golang:1.8.0-stretch -RUN apt-get update && \ - apt-get install -y \ - curl \ - file \ - git \ - jq \ - libprotobuf-dev \ - make \ - protobuf-compiler \ - python-pip \ - python-requests \ - python-yaml \ - unzip && \ - rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -RUN pip install attrs pyhcl -RUN curl -fsSLo shfmt https://github.com/mvdan/sh/releases/download/v1.3.0/shfmt_v1.3.0_linux_amd64 && \ - echo "b1925c2c405458811f0c227266402cf1868b4de529f114722c2e3a5af4ac7bb2 shfmt" | sha256sum -c && \ - chmod +x shfmt && \ - mv shfmt /usr/bin -RUN go clean -i net && \ - go install -tags netgo std && \ - go install -race -tags netgo std -RUN go get -tags netgo \ - github.com/FiloSottile/gvt \ - github.com/client9/misspell/cmd/misspell \ - github.com/fatih/hclfmt \ - github.com/fzipp/gocyclo \ - github.com/gogo/protobuf/gogoproto \ - github.com/gogo/protobuf/protoc-gen-gogoslick \ - github.com/golang/dep/... \ - github.com/golang/lint/golint \ - github.com/golang/protobuf/protoc-gen-go \ - github.com/kisielk/errcheck \ - github.com/mjibson/esc \ - github.com/prometheus/prometheus/cmd/promtool && \ - rm -rf /go/pkg /go/src -RUN mkdir protoc && \ - cd protoc && \ - curl -O -L https://github.com/google/protobuf/releases/download/v3.1.0/protoc-3.1.0-linux-x86_64.zip && \ - unzip protoc-3.1.0-linux-x86_64.zip && \ - cp bin/protoc /usr/bin/ && \ - chmod o+x /usr/bin/protoc && \ - cd .. && \ - rm -rf protoc -RUN mkdir -p /var/run/secrets/kubernetes.io/serviceaccount && \ - touch /var/run/secrets/kubernetes.io/serviceaccount/token -COPY build.sh / -ENTRYPOINT ["/build.sh"] diff --git a/tools/build/golang/build.sh b/tools/build/golang/build.sh deleted file mode 100755 index cf70e1c5..00000000 --- a/tools/build/golang/build.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -set -eu - -if [ -n "${SRC_NAME:-}" ]; then - SRC_PATH=${SRC_PATH:-$GOPATH/src/$SRC_NAME} -elif [ -z "${SRC_PATH:-}" ]; then - echo "Must set either \$SRC_NAME or \$SRC_PATH." - exit 1 -fi - -# If we run make directly, any files created on the bind mount -# will have awkward ownership. So we switch to a user with the -# same user and group IDs as source directory. We have to set a -# few things up so that sudo works without complaining later on. -uid=$(stat --format="%u" "$SRC_PATH") -gid=$(stat --format="%g" "$SRC_PATH") -echo "weave:x:$uid:$gid::$SRC_PATH:/bin/sh" >>/etc/passwd -echo "weave:*:::::::" >>/etc/shadow -echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers - -su weave -c "PATH=$PATH make -C $SRC_PATH BUILD_IN_CONTAINER=false $*" diff --git a/tools/build/haskell/Dockerfile b/tools/build/haskell/Dockerfile deleted file mode 100644 index 8d40c662..00000000 --- a/tools/build/haskell/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM fpco/stack-build:lts-8.9 -COPY build.sh / -COPY copy-libraries /usr/local/bin/ -ENTRYPOINT ["/build.sh"] diff --git a/tools/build/haskell/build.sh b/tools/build/haskell/build.sh deleted file mode 100755 index e80d2abb..00000000 --- a/tools/build/haskell/build.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh -# -# Build a static Haskell binary using stack. - -set -eu - -if [ -z "${SRC_PATH:-}" ]; then - echo "Must set \$SRC_PATH." - exit 1 -fi - -make -C "$SRC_PATH" BUILD_IN_CONTAINER=false "$@" diff --git a/tools/build/haskell/copy-libraries b/tools/build/haskell/copy-libraries deleted file mode 100755 index 18cbba60..00000000 --- a/tools/build/haskell/copy-libraries +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash -# -# Copy dynamically linked libraries for a binary, so we can assemble a Docker -# image. -# -# Run with: -# copy-libraries /path/to/binary /output/dir -# -# Dependencies: -# - awk -# - cp -# - grep -# - ldd -# - mkdir - -set -o errexit -set -o nounset -set -o pipefail - -# Path to a Linux binary that we're going to run in the container. -binary_path="${1}" -# Path to directory to write the output to. -output_dir="${2}" - -exe_name=$(basename "${binary_path}") - -# Identify linked libraries. -libraries=($(ldd "${binary_path}" | awk '{print $(NF-1)}' | grep -v '=>')) -# Add /bin/sh, which we need for Docker imports. -libraries+=('/bin/sh') - -mkdir -p "${output_dir}" - -# Copy executable and all needed libraries into temporary directory. -cp "${binary_path}" "${output_dir}/${exe_name}" -for lib in "${libraries[@]}"; do - mkdir -p "${output_dir}/$(dirname "$lib")" - # Need -L to make sure we get actual libraries & binaries, not symlinks to - # them. - cp -L "${lib}" "${output_dir}/${lib}" -done diff --git a/tools/circle.yml b/tools/circle.yml deleted file mode 100644 index 976a68cc..00000000 --- a/tools/circle.yml +++ /dev/null @@ -1,55 +0,0 @@ -machine: - services: - - docker - environment: - GOPATH: /home/ubuntu - SRCDIR: /home/ubuntu/src/github.com/weaveworks/tools - PATH: $PATH:$HOME/bin - -dependencies: - post: - - sudo chmod a+wr --recursive /usr/local/go/pkg - - go clean -i net - - go install -tags netgo std - - mkdir -p $(dirname $SRCDIR) - - cp -r $(pwd)/ $SRCDIR - - | - curl -fsSLo shfmt https://github.com/mvdan/sh/releases/download/v1.3.0/shfmt_v1.3.0_linux_amd64 && \ - echo "b1925c2c405458811f0c227266402cf1868b4de529f114722c2e3a5af4ac7bb2 shfmt" | sha256sum -c && \ - chmod +x shfmt && \ - sudo mv shfmt /usr/bin - - | - cd $SRCDIR; - go get \ - github.com/fzipp/gocyclo \ - github.com/golang/lint/golint \ - github.com/kisielk/errcheck \ - github.com/fatih/hclfmt - - pip install yapf==0.16.2 flake8==3.3.0 - -test: - override: - - cd $SRCDIR; ./lint . - - cd $SRCDIR/cover; make - - cd $SRCDIR/socks; make - - cd $SRCDIR/runner; make - - cd $SRCDIR/build; make - -deployment: - snapshot: - branch: master - commands: - - docker login -e "$DOCKER_REGISTRY_EMAIL" -u "$DOCKER_REGISTRY_USER" -p "$DOCKER_REGISTRY_PASS" "$DOCKER_REGISTRY_URL" - - | - cd $SRCDIR/build; - for image in $(make images); do - # Tag the built images with the revision of this repo. - docker push "${image}:${GIT_TAG}" - - # Tag the built images with something derived from the base images in - # their respective Dockerfiles. So "FROM golang:1.8.0-stretch" as a - # base image would lead to a tag of "1.8.0-stretch" - IMG_TAG=$(make "imagetag-${image#quay.io/weaveworks/build-}") - docker tag "${image}:latest" "${image}:${IMG_TAG}" - docker push "${image}:${IMG_TAG}" - done diff --git a/tools/config_management/README.md b/tools/config_management/README.md deleted file mode 100644 index bf1f6f65..00000000 --- a/tools/config_management/README.md +++ /dev/null @@ -1,141 +0,0 @@ -# Weaveworks configuration management - -## Introduction - -This project allows you to configure a machine with: - -* Docker and Weave Net for development: `setup_weave-net_dev.yml` -* Docker and Weave Net for testing: `setup_weave-net_test.yml` -* Docker, Kubernetes and Weave Kube (CNI plugin): `setup_weave-kube.yml` - -You can then use these environments for development, testing and debugging. - -## Set up - -You will need [Python](https://www.python.org/downloads/) and [Ansible 2.+](http://docs.ansible.com/ansible/intro_installation.html) installed on your machine and added to your `PATH` in order to be able to configure environments automatically. - -* On any platform, if you have Python installed: `pip install ansible` -* On macOS: `brew install ansible` -* On Linux (via Aptitude): `sudo apt install ansible` -* On Linux (via YUM): `sudo yum install ansible` -* For other platforms or more details, see [here](http://docs.ansible.com/ansible/intro_installation.html) - -Frequent errors during installation are: - -* `fatal error: Python.h: No such file or directory`: install `python-dev` -* `fatal error: ffi.h: No such file or directory`: install `libffi-dev` -* `fatal error: openssl/opensslv.h: No such file or directory`: install `libssl-dev` - -Full steps for a blank Ubuntu/Debian Linux machine: - - sudo apt-get install -qq -y python-pip python-dev libffi-dev libssl-dev - sudo pip install -U cffi - sudo pip install ansible - -## Tags - -These can be used to selectively run (`--tags "tag1,tag2"`) or skip (`--skip-tags "tag1,tag2"`) tasks. - - * `output`: print potentially useful output from hosts (e.g. output of `kubectl get pods --all-namespaces`) - -## Usage - -### Local machine - -``` -ansible-playbook -u -i "localhost", -c local setup_weave-kube.yml -``` - -### Vagrant - -Provision your local VM using Vagrant: - -``` -cd $(mktemp -d -t XXX) -vagrant init ubuntu/xenial64 # or, e.g. centos/7 -vagrant up -``` - -then set the following environment variables by extracting the output of `vagrant ssh-config`: - -``` -eval $(vagrant ssh-config | sed \ --ne 's/\ *HostName /vagrant_ssh_host=/p' \ --ne 's/\ *User /vagrant_ssh_user=/p' \ --ne 's/\ *Port /vagrant_ssh_port=/p' \ --ne 's/\ *IdentityFile /vagrant_ssh_id_file=/p') -``` - -and finally run: - -``` -ansible-playbook --private-key=$vagrant_ssh_id_file -u $vagrant_ssh_user \ ---ssh-extra-args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ --i "$vagrant_ssh_host:$vagrant_ssh_port," setup_weave-kube.yml -``` - -or, for specific versions of Kubernetes and Docker: - -``` -ansible-playbook --private-key=$vagrant_ssh_id_file -u $vagrant_ssh_user \ ---ssh-extra-args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ --i "$vagrant_ssh_host:$vagrant_ssh_port," setup_weave-kube.yml \ ---extra-vars "docker_version=1.12.3 kubernetes_version=1.4.4" -``` - -NOTE: Kubernetes APT repo includes only the latest version, so currently -retrieving an older version will fail. - -### Terraform - -Provision your machine using the Terraform scripts from `../provisioning`, then run: - -``` -terraform output ansible_inventory > /tmp/ansible_inventory -``` - -and - -``` -ansible-playbook \ - --private-key="$(terraform output private_key_path)" \ - -u "$(terraform output username)" \ - -i /tmp/ansible_inventory \ - --ssh-extra-args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" \ - ../../config_management/setup_weave-kube.yml - -``` - -To specify versions of Kubernetes and Docker see Vagrant examples above. - -N.B.: `--ssh-extra-args` is used to provide: - -* `StrictHostKeyChecking=no`: as VMs come and go, the same IP can be used by a different machine, so checking the host's SSH key may fail. Note that this introduces a risk of a man-in-the-middle attack. -* `UserKnownHostsFile=/dev/null`: if you previously connected a VM with the same IP but a different public key, and added it to `~/.ssh/known_hosts`, SSH may still fail to connect, hence we use `/dev/null` instead of `~/.ssh/known_hosts`. - - -### Docker installation role - -Various ways to install Docker are provided: - -- `docker-from-docker-ce-repo` -- `docker-from-docker-repo` -- `docker-from-get.docker.com` -- `docker-from-tarball` - -each producing a slightly different outcome, which can be useful for testing various setup scenarios. - -The `docker-install` role selects one of the above ways to install Docker based on the `docker_install_role` variable. -The default value for this variable is configured in `group_vars/all`. -You can however override it with whichever role you would want to run by passing the name of the role as a key-value pair in `extra-vars`, e.g.: - -``` -ansible-playbook .yml \ - --extra-vars "docker_install_role=docker-from-docker-ce-repo" -``` - - -## Resources - -* [https://www.vagrantup.com/docs/provisioning/ansible.html](https://www.vagrantup.com/docs/provisioning/ansible.html) -* [http://docs.ansible.com/ansible/guide_vagrant.html](http://docs.ansible.com/ansible/guide_vagrant.html) diff --git a/tools/config_management/group_vars/all b/tools/config_management/group_vars/all deleted file mode 100644 index d728cce8..00000000 --- a/tools/config_management/group_vars/all +++ /dev/null @@ -1,11 +0,0 @@ ---- -go_version: 1.8.1 -terraform_version: 0.8.5 -docker_version: 17.06 -docker_install_role: 'docker-from-docker-ce-repo' -kubernetes_version: 1.6.1 -kubernetes_cni_version: 0.5.1 -kubernetes_token: '123456.0123456789123456' -etcd_container_version: 2.2.5 -kube_discovery_container_version: 1.0 -pause_container_version: 3.0 diff --git a/tools/config_management/library/setup_ansible_dependencies.yml b/tools/config_management/library/setup_ansible_dependencies.yml deleted file mode 100644 index 50263369..00000000 --- a/tools/config_management/library/setup_ansible_dependencies.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -################################################################################ -# Install Ansible's dependencies: python and lsb_release, required respectively -# to run Ansible modules and gather Ansible facts. -# -# See also: -# - http://docs.ansible.com/ansible/intro_installation.html#managed-node-requirements -# - http://docs.ansible.com/ansible/setup_module.html -################################################################################ - -- name: check if python is installed (as required by ansible modules) - raw: test -e /usr/bin/python - register: is_python_installed - failed_when: is_python_installed.rc not in [0, 1] - changed_when: false # never mutates state. - -- name: install python if missing (as required by ansible modules) - when: is_python_installed|failed # skip otherwise - raw: (test -e /usr/bin/apt-get && apt-get update && apt-get install -y python-minimal) || (test -e /usr/bin/yum && yum update && yum install -y python) - changed_when: is_python_installed.rc == 1 - -- name: check if lsb_release is installed (as required for ansible facts) - raw: test -e /usr/bin/lsb_release - register: is_lsb_release_installed - failed_when: is_lsb_release_installed.rc not in [0, 1] - changed_when: false # never mutates state. - -- name: install lsb_release if missing (as required for ansible facts) - when: is_lsb_release_installed|failed # skip otherwise - raw: (test -e /usr/bin/apt-get && apt-get install -y lsb_release) || (test -e /usr/bin/yum && yum install -y redhat-lsb-core) - changed_when: is_lsb_release_installed.rc == 1 - -- setup: # gather 'facts', i.e. compensates for 'gather_facts: false' in calling playbook. diff --git a/tools/config_management/roles/dev-tools/files/apt-daily.timer.conf b/tools/config_management/roles/dev-tools/files/apt-daily.timer.conf deleted file mode 100644 index bd19c61f..00000000 --- a/tools/config_management/roles/dev-tools/files/apt-daily.timer.conf +++ /dev/null @@ -1,2 +0,0 @@ -[Timer] -Persistent=false diff --git a/tools/config_management/roles/dev-tools/tasks/main.yml b/tools/config_management/roles/dev-tools/tasks/main.yml deleted file mode 100644 index 96ac3a21..00000000 --- a/tools/config_management/roles/dev-tools/tasks/main.yml +++ /dev/null @@ -1,48 +0,0 @@ ---- -# Set up Development Environment. - -- name: install development tools - package: - name: "{{ item }}" - state: present - with_items: - # weave net dependencies - - make - - vagrant - # ansible dependencies - - python-pip - - python-dev - - libffi-dev - - libssl-dev - # terraform dependencies - - unzip - # other potentially useful tools: - - aufs-tools - - ethtool - - iputils-arping - - libpcap-dev - - git - - mercurial - - bc - - jq - -- name: install ansible - pip: - name: ansible - state: present - -- name: install terraform - unarchive: - src: 'https://releases.hashicorp.com/terraform/{{ terraform_version }}/terraform_{{ terraform_version }}_linux_{{ {"x86_64": "amd64", "i386": "386"}[ansible_architecture] }}.zip' - remote_src: yes - dest: /usr/bin - mode: 0555 - creates: /usr/bin/terraform - -# Ubuntu runs an apt update process that will run on first boot from image. -# This is of questionable value when the machines are only going to live for a few minutes. -# If you leave them on they will run the process daily. -# Also we have seen the update process create a 'defunct' process which then throws off Weave Net smoke-test checks. -# So, we override the 'persistent' setting so it will still run at the scheduled time but will not try to catch up on first boot. -- name: copy apt daily override - copy: src=apt-daily.timer.conf dest=/etc/systemd/system/apt-daily.timer.d/ diff --git a/tools/config_management/roles/docker-configuration/files/docker.conf b/tools/config_management/roles/docker-configuration/files/docker.conf deleted file mode 100644 index 626d8022..00000000 --- a/tools/config_management/roles/docker-configuration/files/docker.conf +++ /dev/null @@ -1,3 +0,0 @@ -[Service] -ExecStart= -ExecStart=/usr/bin/dockerd -H fd:// -H unix:///var/run/alt-docker.sock -H tcp://0.0.0.0:2375 -s overlay --insecure-registry "weave-ci-registry:5000" diff --git a/tools/config_management/roles/docker-configuration/tasks/main.yml b/tools/config_management/roles/docker-configuration/tasks/main.yml deleted file mode 100644 index d6736968..00000000 --- a/tools/config_management/roles/docker-configuration/tasks/main.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -# Configure Docker -# See also: https://docs.docker.com/engine/installation/linux/ubuntulinux/#install - -- name: ensure docker group is present (or create it) - group: - name: docker - state: present - -- name: add user to docker group (avoids sudo-ing) - user: - name: "{{ ansible_user }}" - group: docker - state: present - -- name: ensure docker's systemd directory exists - file: - path: /etc/systemd/system/docker.service.d - state: directory - recurse: yes - when: ansible_os_family != "RedHat" - -- name: enable docker remote api over tcp - copy: - src: "{{ role_path }}/files/docker.conf" - dest: /etc/systemd/system/docker.service.d/docker.conf - register: docker_conf - when: ansible_os_family != "RedHat" - -- name: restart docker service - systemd: - name: docker - state: restarted - daemon_reload: yes # ensure docker.conf is picked up. - enabled: yes - when: docker_conf.changed or ansible_os_family == "RedHat" diff --git a/tools/config_management/roles/docker-from-docker-ce-repo/tasks/debian.yml b/tools/config_management/roles/docker-from-docker-ce-repo/tasks/debian.yml deleted file mode 100644 index 3e2ae127..00000000 --- a/tools/config_management/roles/docker-from-docker-ce-repo/tasks/debian.yml +++ /dev/null @@ -1,35 +0,0 @@ ---- -# Debian / Ubuntu specific: - -- name: install dependencies for docker repository - package: - name: "{{ item }}" - state: present - with_items: - - apt-transport-https - - ca-certificates - -- name: add apt key for the docker repository - apt_key: - keyserver: hkp://ha.pool.sks-keyservers.net:80 - id: 9DC858229FC7DD38854AE2D88D81803C0EBFCD88 - state: present - register: apt_key_docker_repo - -- name: add docker's apt repository ({{ ansible_distribution | lower }}-{{ ansible_distribution_release }}) - apt_repository: - repo: deb https://download.docker.com/linux/ubuntu {{ ansible_lsb.codename|lower }} stable - state: present - register: apt_docker_repo - -- name: update apt's cache - apt: - update_cache: yes - when: apt_key_docker_repo.changed or apt_docker_repo.changed - -- name: install docker-engine - package: - name: "{{ item }}" - state: present - with_items: - - docker-ce={{ docker_version }}* diff --git a/tools/config_management/roles/docker-from-docker-ce-repo/tasks/main.yml b/tools/config_management/roles/docker-from-docker-ce-repo/tasks/main.yml deleted file mode 100644 index 0acb6d8c..00000000 --- a/tools/config_management/roles/docker-from-docker-ce-repo/tasks/main.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -# Set up Docker -# See also: https://docs.docker.com/engine/installation/linux/ubuntulinux/#install - -# Distribution-specific tasks: -- include: debian.yml - when: ansible_os_family == "Debian" - -- include: redhat.yml - when: ansible_os_family == "RedHat" diff --git a/tools/config_management/roles/docker-from-docker-ce-repo/tasks/redhat.yml b/tools/config_management/roles/docker-from-docker-ce-repo/tasks/redhat.yml deleted file mode 100644 index ea9a3fa4..00000000 --- a/tools/config_management/roles/docker-from-docker-ce-repo/tasks/redhat.yml +++ /dev/null @@ -1,29 +0,0 @@ -# Docker installation from Docker's CentOS Community Edition -# See also: https://docs.docker.com/engine/installation/linux/centos/ - -- name: remove all potentially pre existing packages - yum: - name: '{{ item }}' - state: absent - with_items: - - docker - - docker-common - - container-selinux - - docker-selinux - - docker-engine - -- name: install yum-utils - yum: - name: yum-utils - state: present - -- name: add docker ce repo - command: yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo - -# Note that Docker CE versions do not follow regular Docker versions, but look -# like, for example: "17.03.0.el7" -- name: install docker - yum: - name: 'docker-ce-{{ docker_version }}' - update_cache: yes - state: present diff --git a/tools/config_management/roles/docker-from-docker-repo/tasks/debian.yml b/tools/config_management/roles/docker-from-docker-repo/tasks/debian.yml deleted file mode 100644 index cc33c2c9..00000000 --- a/tools/config_management/roles/docker-from-docker-repo/tasks/debian.yml +++ /dev/null @@ -1,35 +0,0 @@ ---- -# Debian / Ubuntu specific: - -- name: install dependencies for docker repository - package: - name: "{{ item }}" - state: present - with_items: - - apt-transport-https - - ca-certificates - -- name: add apt key for the docker repository - apt_key: - keyserver: hkp://ha.pool.sks-keyservers.net:80 - id: 58118E89F3A912897C070ADBF76221572C52609D - state: present - register: apt_key_docker_repo - -- name: add docker's apt repository ({{ ansible_distribution | lower }}-{{ ansible_distribution_release }}) - apt_repository: - repo: deb https://apt.dockerproject.org/repo {{ ansible_distribution | lower }}-{{ ansible_distribution_release }} main - state: present - register: apt_docker_repo - -- name: update apt's cache - apt: - update_cache: yes - when: apt_key_docker_repo.changed or apt_docker_repo.changed - -- name: install docker-engine - package: - name: "{{ item }}" - state: present - with_items: - - docker-engine={{ docker_version }}* diff --git a/tools/config_management/roles/docker-from-docker-repo/tasks/main.yml b/tools/config_management/roles/docker-from-docker-repo/tasks/main.yml deleted file mode 100644 index 0acb6d8c..00000000 --- a/tools/config_management/roles/docker-from-docker-repo/tasks/main.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -# Set up Docker -# See also: https://docs.docker.com/engine/installation/linux/ubuntulinux/#install - -# Distribution-specific tasks: -- include: debian.yml - when: ansible_os_family == "Debian" - -- include: redhat.yml - when: ansible_os_family == "RedHat" diff --git a/tools/config_management/roles/docker-from-docker-repo/tasks/redhat.yml b/tools/config_management/roles/docker-from-docker-repo/tasks/redhat.yml deleted file mode 100644 index d29964e1..00000000 --- a/tools/config_management/roles/docker-from-docker-repo/tasks/redhat.yml +++ /dev/null @@ -1,25 +0,0 @@ ---- -# RedHat / CentOS specific: - -- name: add docker' yum repository (centos/{{ ansible_lsb.major_release }}) - yum_repository: - name: docker - description: Docker YUM repo - file: external_repos - baseurl: https://yum.dockerproject.org/repo/main/centos/{{ ansible_lsb.major_release }} - enabled: yes - gpgkey: https://yum.dockerproject.org/gpg - gpgcheck: yes - state: present - -- name: update yum's cache - yum: - name: "*" - update_cache: yes - -- name: install docker-engine - package: - name: "{{ item }}" - state: present - with_items: - - docker-engine-{{ docker_version }} diff --git a/tools/config_management/roles/docker-from-get.docker.com/tasks/debian.yml b/tools/config_management/roles/docker-from-get.docker.com/tasks/debian.yml deleted file mode 100644 index 7444194e..00000000 --- a/tools/config_management/roles/docker-from-get.docker.com/tasks/debian.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -# Debian / Ubuntu specific: - -- name: apt-import gpg key for the docker repository - shell: curl -sSL https://get.docker.com/gpg | sudo apt-key add - - -- name: install docker - shell: 'curl -sSL https://get.docker.com/ | sed -e s/docker-engine/docker-engine={{ docker_version }}*/ -e s/docker-ce/docker-ce={{ docker_version }}*/ | sh' diff --git a/tools/config_management/roles/docker-from-get.docker.com/tasks/main.yml b/tools/config_management/roles/docker-from-get.docker.com/tasks/main.yml deleted file mode 100644 index 92c497b7..00000000 --- a/tools/config_management/roles/docker-from-get.docker.com/tasks/main.yml +++ /dev/null @@ -1,10 +0,0 @@ ---- -# Set up Docker -# See also: legacy gce.sh script - -# Distribution-specific tasks: -- include: debian.yml - when: ansible_os_family == "Debian" - -- include: redhat.yml - when: ansible_os_family == "RedHat" diff --git a/tools/config_management/roles/docker-from-get.docker.com/tasks/redhat.yml b/tools/config_management/roles/docker-from-get.docker.com/tasks/redhat.yml deleted file mode 100644 index ea7cbfc4..00000000 --- a/tools/config_management/roles/docker-from-get.docker.com/tasks/redhat.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -# RedHat / CentOS specific: - -- name: rpm-import gpg key for the docker repository - shell: curl -sSLo /tmp/docker.gpg https://get.docker.com/gpg && sudo rpm --import /tmp/docker.gpg - -- name: install docker - shell: 'curl -sSL https://get.docker.com/ | sed -e s/docker-engine/docker-engine-{{ docker_version }}*/ | sh' - -- name: wait for docker installation to complete - shell: yum install -y yum-utils && yum-complete-transaction diff --git a/tools/config_management/roles/docker-from-tarball/tasks/main.yml b/tools/config_management/roles/docker-from-tarball/tasks/main.yml deleted file mode 100644 index a233d10a..00000000 --- a/tools/config_management/roles/docker-from-tarball/tasks/main.yml +++ /dev/null @@ -1,61 +0,0 @@ ---- -# Set up Docker -# See also: -# - https://docs.docker.com/engine/installation/linux/ubuntulinux/#install -# - https://github.com/docker/docker/releases - -- include_role: - name: docker-prerequisites - -- name: install daemon - package: - name: daemon - state: present - -- name: 'create directory {{ docker_dir }}/{{ docker_version }}' - file: - path: '{{ docker_dir }}/{{ docker_version }}' - state: directory - mode: 0755 - -- name: download and extract docker - unarchive: - src: 'https://get.docker.com/builds/Linux/x86_64/docker-{{ docker_version }}.tgz' - remote_src: yes - dest: '{{ docker_dir }}/{{ docker_version }}' - extra_opts: '--strip-components=1' - mode: 0555 - creates: '{{ docker_dir }}/{{ docker_version }}/docker' - -- name: create symlink to current version - file: - src: '{{ docker_dir }}/{{ docker_version }}' - dest: '{{ docker_dir }}/current' - state: link - mode: 0555 - -- name: list all files to symlink - find: - paths: '{{ docker_dir }}/current' - file_type: file - register: binaries - changed_when: false - -- name: create symlinks to all binaries - file: - src: '{{ item }}' - dest: /usr/bin/{{ item | basename }} - state: link - with_items: "{{ binaries.files | map(attribute='path') | list }}" - -- name: killall docker - command: killall docker - register: killall - failed_when: false - changed_when: killall.rc == 0 - -- name: start dockerd - command: daemon -- /usr/bin/dockerd - -- include_role: - name: docker-configuration diff --git a/tools/config_management/roles/docker-from-tarball/vars/main.yml b/tools/config_management/roles/docker-from-tarball/vars/main.yml deleted file mode 100644 index d4106684..00000000 --- a/tools/config_management/roles/docker-from-tarball/vars/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -docker_dir: '/opt/docker' -docker_url: '{{ "rc" in {{ docker_version }} | ternary( > - "https://test.docker.com/builds/Linux/x86_64/docker-{{ docker_version }}.tgz", > - "https://get.docker.com/builds/Linux/x86_64/docker-{{ docker_version }}.tgz") }}' diff --git a/tools/config_management/roles/docker-install/tasks/main.yml b/tools/config_management/roles/docker-install/tasks/main.yml deleted file mode 100644 index cc803cab..00000000 --- a/tools/config_management/roles/docker-install/tasks/main.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -# Set up Docker - -- include_role: - name: docker-prerequisites - -# Dynamically include docker installation role using 'when' as Ansible does not -# allow for include_role's name to be set to a variable. Indeed: -# - include_role: -# name: '{{ docker_install_role }}' -# fails with: -# ERROR! 'docker_install_role' is undefined -- include_role: - name: docker-from-docker-repo - when: docker_install_role == 'docker-from-docker-repo' - -- include_role: - name: docker-from-docker-ce-repo - when: docker_install_role == 'docker-from-docker-ce-repo' - -- include_role: - name: docker-from-get.docker.com - when: docker_install_role == 'docker-from-get.docker.com' - -- include_role: - name: docker-from-tarball - when: docker_install_role == 'docker-from-tarball' - -- include_role: - name: docker-configuration diff --git a/tools/config_management/roles/docker-prerequisites/tasks/debian.yml b/tools/config_management/roles/docker-prerequisites/tasks/debian.yml deleted file mode 100644 index 48b0c2e3..00000000 --- a/tools/config_management/roles/docker-prerequisites/tasks/debian.yml +++ /dev/null @@ -1,11 +0,0 @@ ---- -# Install Docker's dependencies -# See also: https://docs.docker.com/engine/installation/linux/ubuntulinux/#install - -- name: install linux-image-extra-*/virtual - package: - name: "{{ item }}" - state: present - with_items: - - linux-image-extra-{{ ansible_kernel }} - - linux-image-extra-virtual diff --git a/tools/config_management/roles/docker-prerequisites/tasks/main.yml b/tools/config_management/roles/docker-prerequisites/tasks/main.yml deleted file mode 100644 index a8177372..00000000 --- a/tools/config_management/roles/docker-prerequisites/tasks/main.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- - -# Distribution-specific tasks: -- include: debian.yml - when: ansible_os_family == "Debian" diff --git a/tools/config_management/roles/golang-from-tarball/tasks/main.yml b/tools/config_management/roles/golang-from-tarball/tasks/main.yml deleted file mode 100644 index 55476bf6..00000000 --- a/tools/config_management/roles/golang-from-tarball/tasks/main.yml +++ /dev/null @@ -1,36 +0,0 @@ ---- -# Set up Go. - -- name: install go - unarchive: - src: 'https://storage.googleapis.com/golang/go{{ go_version }}.linux-{{ {"x86_64": "amd64", "i386": "386"}[ansible_architecture] }}.tar.gz' - remote_src: yes - dest: /usr/local - mode: 0777 - creates: /usr/local/go/bin/go - -- name: set go env. vars. and add go to path - blockinfile: - dest: '$HOME/.bashrc' - block: | - export PATH=$PATH:/usr/local/go/bin - export GOPATH=$HOME - state: present - create: yes - mode: 0644 - become: '{{ item }}' - with_items: - - true # Run as root - - false # Run as SSH user - -- name: source ~/.bashrc from ~/.bash_profile - lineinfile: - dest: '$HOME/.bash_profile' - line: '[ -r $HOME/.bashrc ] && source $HOME/.bashrc' - state: present - create: yes - mode: 0644 - become: '{{ item }}' - with_items: - - true # Run as root - - false # Run as SSH user diff --git a/tools/config_management/roles/kubelet-stop/tasks/main.yml b/tools/config_management/roles/kubelet-stop/tasks/main.yml deleted file mode 100644 index 6e5f3148..00000000 --- a/tools/config_management/roles/kubelet-stop/tasks/main.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- - -- name: check if kubelet service exists - stat: - path: /etc/init.d/kubelet - register: kubelet - -# avoids having weave-net and weave-kube conflict in some test cases (e.g. 130_expose_test.sh) -- name: stop kubelet service - systemd: - name: kubelet - state: stopped - enabled: no - when: kubelet.stat.exists diff --git a/tools/config_management/roles/kubernetes-docker-images/tasks/main.yml b/tools/config_management/roles/kubernetes-docker-images/tasks/main.yml deleted file mode 100644 index 801c4637..00000000 --- a/tools/config_management/roles/kubernetes-docker-images/tasks/main.yml +++ /dev/null @@ -1,14 +0,0 @@ ---- - -- name: docker pull images used by k8s tests - docker_image: - name: '{{ item }}' - state: present - with_items: - - gcr.io/google_containers/etcd-amd64:{{ etcd_container_version }} - - gcr.io/google_containers/kube-apiserver-amd64:v{{ kubernetes_version }} - - gcr.io/google_containers/kube-controller-manager-amd64:v{{ kubernetes_version }} - - gcr.io/google_containers/kube-proxy-amd64:v{{ kubernetes_version }} - - gcr.io/google_containers/kube-scheduler-amd64:v{{ kubernetes_version }} - - gcr.io/google_containers/kube-discovery-amd64:{{ kube_discovery_container_version }} - - gcr.io/google_containers/pause-amd64:{{ pause_container_version }} diff --git a/tools/config_management/roles/kubernetes-install/tasks/debian.yml b/tools/config_management/roles/kubernetes-install/tasks/debian.yml deleted file mode 100644 index 9f16edfd..00000000 --- a/tools/config_management/roles/kubernetes-install/tasks/debian.yml +++ /dev/null @@ -1,37 +0,0 @@ ---- -# Debian / Ubuntu specific: - -- name: add apt key for the kubernetes repository - apt_key: - url: https://packages.cloud.google.com/apt/doc/apt-key.gpg - state: present - register: apt_key_k8s_repo - -- name: add kubernetes' apt repository (kubernetes-{{ ansible_distribution_release }}) - apt_repository: - repo: deb http://apt.kubernetes.io/ kubernetes-{{ ansible_distribution_release }} main - state: present - register: apt_k8s_repo - when: '"alpha" not in kubernetes_version and "beta" not in kubernetes_version' - -- name: add kubernetes' apt repository (kubernetes-{{ ansible_distribution_release }}-unstable) - apt_repository: - repo: deb http://apt.kubernetes.io/ kubernetes-{{ ansible_distribution_release }}-unstable main - state: present - register: apt_k8s_repo - when: '"alpha" in kubernetes_version or "beta" in kubernetes_version' - -- name: update apt's cache - apt: - update_cache: yes - when: apt_key_k8s_repo.changed or apt_k8s_repo.changed - -- name: install kubelet and kubectl - package: - name: "{{ item }}" - state: present - with_items: - - kubelet={{ kubernetes_version }}* - - kubectl={{ kubernetes_version }}* - - kubeadm={{ kubernetes_version }}* - - kubernetes-cni={{ kubernetes_cni_version }}* diff --git a/tools/config_management/roles/kubernetes-install/tasks/main.yml b/tools/config_management/roles/kubernetes-install/tasks/main.yml deleted file mode 100644 index 50dcddaf..00000000 --- a/tools/config_management/roles/kubernetes-install/tasks/main.yml +++ /dev/null @@ -1,16 +0,0 @@ ---- -# Install Kubernetes - -# Distribution-specific tasks: -- include: debian.yml - when: ansible_os_family == "Debian" - -- include: redhat.yml - when: ansible_os_family == "RedHat" - -- name: install ebtables - package: - name: "{{ item }}" - state: present - with_items: - - ebtables diff --git a/tools/config_management/roles/kubernetes-install/tasks/redhat.yml b/tools/config_management/roles/kubernetes-install/tasks/redhat.yml deleted file mode 100644 index 293729dc..00000000 --- a/tools/config_management/roles/kubernetes-install/tasks/redhat.yml +++ /dev/null @@ -1,30 +0,0 @@ ---- -# RedHat / CentOS specific: - -- name: add kubernetes' yum repository (kubernetes-el{{ ansible_lsb.major_release }}-x86-64) - yum_repository: - name: kubernetes - description: Kubernetes YUM repo - file: external_repos - baseurl: https://packages.cloud.google.com/yum/repos/kubernetes-el{{ ansible_lsb.major_release }}-x86_64 - enabled: yes - gpgkey: https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg - gpgcheck: yes - state: present - register: yum_k8s_repo - -- name: update yum's cache - yum: - name: "*" - update_cache: yes - when: yum_k8s_repo.changed - -- name: install kubelet and kubectl - package: - name: "{{ item }}" - state: present - with_items: - - kubelet-{{ kubernetes_version }}* - - kubectl-{{ kubernetes_version }}* - - kubeadm-{{ kubernetes_version }}* - - kubernetes-cni-{{ kubernetes_cni_version }}* diff --git a/tools/config_management/roles/kubernetes-start/tasks/main.yml b/tools/config_management/roles/kubernetes-start/tasks/main.yml deleted file mode 100644 index d343b21c..00000000 --- a/tools/config_management/roles/kubernetes-start/tasks/main.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -# Start Kubernetes - -- name: kubeadm reset - command: kubeadm reset - -- name: restart kubelet service - systemd: - name: kubelet - state: restarted - enabled: yes - -- name: optionally set kubeconfig option - set_fact: - kubeconfig: '{{ (kubernetes_version >= "1.5.4") | ternary("--kubeconfig /etc/kubernetes/admin.conf", "") }}' - kubernetes_version_option: '{{ (kubernetes_version >= "1.6") | ternary("kubernetes_version", "use-kubernetes-version") }}' - -- name: kubeadm init on the master - command: 'kubeadm init --{{ kubernetes_version_option }}=v{{ kubernetes_version }} --token={{ kubernetes_token }}' - when: ' {{ play_hosts[0] == inventory_hostname }}' - -- name: allow pods to be run on the master (if only node) - command: 'kubectl {{ kubeconfig }} taint nodes --all {{ (kubernetes_version < "1.6") | ternary("dedicated-", "node-role.kubernetes.io/master:NoSchedule-") }}' - when: '{{ play_hosts | length }} == 1' - -- name: kubeadm join on workers - command: 'kubeadm join --token={{ kubernetes_token }} {{ hostvars[play_hosts[0]].private_ip }}{{ (kubernetes_version > "1.6") | ternary(":6443", "") }}' - when: ' {{ play_hosts[0] != inventory_hostname }}' - -- name: list kubernetes' pods - command: kubectl {{ kubeconfig }} get pods --all-namespaces - when: ' {{ play_hosts[0] == inventory_hostname }}' - changed_when: false - register: kubectl_get_pods - tags: - - output - -- name: print outpout of `kubectl get pods --all-namespaces` - debug: msg="{{ kubectl_get_pods.stdout_lines }}" - when: ' {{ play_hosts[0] == inventory_hostname }}' - tags: - - output diff --git a/tools/config_management/roles/setup-ansible/pre_tasks/main.yml b/tools/config_management/roles/setup-ansible/pre_tasks/main.yml deleted file mode 100644 index efb15491..00000000 --- a/tools/config_management/roles/setup-ansible/pre_tasks/main.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -# Set machine up to be able to run ansible playbooks. - -- name: check if python is installed (as required by ansible modules) - raw: test -e /usr/bin/python - register: is_python_installed - failed_when: is_python_installed.rc not in [0, 1] - changed_when: false # never mutates state. - -- name: install python if missing (as required by ansible modules) - when: is_python_installed|failed # skip otherwise - raw: (test -e /usr/bin/apt-get && apt-get install -y python-minimal) || (test -e /usr/bin/yum && yum install -y python) - changed_when: is_python_installed.rc == 1 - -- name: check if lsb_release is installed (as required for ansible facts) - raw: test -e /usr/bin/lsb_release - register: is_lsb_release_installed - failed_when: is_lsb_release_installed.rc not in [0, 1] - changed_when: false # never mutates state. - -- name: install lsb_release if missing (as required for ansible facts) - when: is_lsb_release_installed|failed # skip otherwise - raw: (test -e /usr/bin/apt-get && apt-get install -y lsb_release) || (test -e /usr/bin/yum && yum install -y lsb_release) - changed_when: is_lsb_release_installed.rc == 1 - -- setup: # gather 'facts', i.e. compensates for the above 'gather_facts: false'. diff --git a/tools/config_management/roles/sock-shop/tasks/tasks.yml b/tools/config_management/roles/sock-shop/tasks/tasks.yml deleted file mode 100644 index 9667ab04..00000000 --- a/tools/config_management/roles/sock-shop/tasks/tasks.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -# Set up sock-shop on top of Kubernetes. -# Dependencies on other roles: -# - kubernetes - -- name: create sock-shop namespace in k8s - command: kubectl --kubeconfig /etc/kubernetes/admin.conf create namespace sock-shop - -- name: create sock-shop in k8s - command: kubectl --kubeconfig /etc/kubernetes/admin.conf apply -n sock-shop -f "https://github.com/microservices-demo/microservices-demo/blob/master/deploy/kubernetes/complete-demo.yaml?raw=true" - -- name: describe front-end service - command: kubectl --kubeconfig /etc/kubernetes/admin.conf describe svc front-end -n sock-shop - changed_when: false - register: kubectl_describe_svc_frontend - tags: - - output - -- name: print outpout of `kubectl describe svc front-end -n sock-shop` - debug: msg="{{ kubectl_describe_svc_frontend.stdout_lines }}" - tags: - - output - -- name: list sock-shop k8s' pods - command: kubectl --kubeconfig /etc/kubernetes/admin.conf get pods -n sock-shop - changed_when: false - register: kubectl_get_pods - tags: - - output - -- name: print outpout of `kubectl get pods -n sock-shop` - debug: msg="{{ kubectl_get_pods.stdout_lines }}" - tags: - - output diff --git a/tools/config_management/roles/weave-kube/tasks/main.yml b/tools/config_management/roles/weave-kube/tasks/main.yml deleted file mode 100644 index e2025eef..00000000 --- a/tools/config_management/roles/weave-kube/tasks/main.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -# Set up Weave Kube on top of Kubernetes. - -- name: set url for weave-kube daemonset - set_fact: - weave_kube_url: '{{ (kubernetes_version < "1.6") | ternary("https://git.io/weave-kube", "https://git.io/weave-kube-1.6") }}' - -- name: configure weave net's cni plugin - command: 'kubectl {{ kubeconfig }} apply -f {{ weave_kube_url }}' - when: '{{ play_hosts[0] == inventory_hostname }}' - -- name: list kubernetes' pods - command: 'kubectl {{ kubeconfig }} get pods --all-namespaces' - when: '{{ play_hosts[0] == inventory_hostname }}' - changed_when: false - register: kubectl_get_pods - tags: - - output - -- name: print outpout of `kubectl get pods --all-namespaces` - debug: msg="{{ kubectl_get_pods.stdout_lines }}" - when: '{{ play_hosts[0] == inventory_hostname }}' - tags: - - output diff --git a/tools/config_management/roles/weave-net-sources/tasks/main.yml b/tools/config_management/roles/weave-net-sources/tasks/main.yml deleted file mode 100644 index b0a7815c..00000000 --- a/tools/config_management/roles/weave-net-sources/tasks/main.yml +++ /dev/null @@ -1,24 +0,0 @@ ---- -# Set up Development Environment for Weave Net. - -- name: check if weave net has been checked out - become: false # Run as SSH-user - stat: - path: $HOME/src/github.com/weaveworks/weave - register: weave - failed_when: false - changed_when: false - -- name: git clone weave net - become: false # Run as SSH-user - git: - repo: https://github.com/weaveworks/weave.git - dest: $HOME/src/github.com/weaveworks/weave - when: not weave.stat.exists - -- name: create a convenience symlink to $HOME/src/github.com/weaveworks/weave - become: false # Run as SSH-user - file: - src: $HOME/src/github.com/weaveworks/weave - dest: $HOME/weave - state: link diff --git a/tools/config_management/roles/weave-net-utilities/tasks/main.yml b/tools/config_management/roles/weave-net-utilities/tasks/main.yml deleted file mode 100644 index 6883d23a..00000000 --- a/tools/config_management/roles/weave-net-utilities/tasks/main.yml +++ /dev/null @@ -1,56 +0,0 @@ ---- - -- name: install epel-release - package: - name: "{{ item }}" - state: present - with_items: - - epel-release - when: ansible_os_family == "RedHat" - -- name: install jq - package: - name: "{{ item }}" - state: present - with_items: - - jq - -- name: install ethtool (used by the weave script) - package: - name: "{{ item }}" - state: present - with_items: - - ethtool - -- name: install nsenter (used by the weave script) - command: docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter - -- name: install pip (for docker-py) - package: - name: "{{ item }}" - state: present - with_items: - - python-pip - -- name: install docker-py (for docker_image) - pip: - name: docker-py - state: present - -- name: docker pull images used by tests - docker_image: - name: '{{ item }}' - state: present - with_items: - - alpine - - aanand/docker-dnsutils - - weaveworks/hello-world - -- name: docker pull docker-py which is used by tests - docker_image: - name: joffrey/docker-py - tag: '{{ item }}' - state: present - with_items: - - '1.8.1' - - '1.9.0-rc2' diff --git a/tools/config_management/roles/weave-net/tasks/main.yml b/tools/config_management/roles/weave-net/tasks/main.yml deleted file mode 100644 index 0ef5e351..00000000 --- a/tools/config_management/roles/weave-net/tasks/main.yml +++ /dev/null @@ -1,26 +0,0 @@ ---- -# Set up Weave Net. - -- name: install weave net - get_url: - url: https://git.io/weave - dest: /usr/local/bin/weave - mode: 0555 - -- name: stop weave net - command: /usr/local/bin/weave stop - -- name: start weave net - command: /usr/local/bin/weave launch - -- name: get weave net's status - command: /usr/local/bin/weave status - changed_when: false - register: weave_status - tags: - - output - -- name: print outpout of `weave status` - debug: msg="{{ weave_status.stdout_lines }}" - tags: - - output diff --git a/tools/config_management/setup_weave-kube.yml b/tools/config_management/setup_weave-kube.yml deleted file mode 100644 index 5c68c978..00000000 --- a/tools/config_management/setup_weave-kube.yml +++ /dev/null @@ -1,27 +0,0 @@ ---- -################################################################################ -# Install Docker and Kubernetes, and configure Kubernetes to -# use Weave Net's CNI plugin (a.k.a. Weave Kube). -# -# See also: -# - http://kubernetes.io/docs/getting-started-guides/kubeadm/ -# - https://github.com/weaveworks/weave-kube -################################################################################ - -- name: install docker, kubernetes and weave-kube - hosts: all - gather_facts: false # required in case Python is not available on the host - become: true - become_user: root - - pre_tasks: - - include: library/setup_ansible_dependencies.yml - - roles: - - docker-install - - weave-net-utilities - - kubernetes-install - - kubernetes-docker-images - - kubelet-stop - - kubernetes-start - - weave-kube diff --git a/tools/config_management/setup_weave-net_debug.yml b/tools/config_management/setup_weave-net_debug.yml deleted file mode 100644 index ff73a527..00000000 --- a/tools/config_management/setup_weave-net_debug.yml +++ /dev/null @@ -1,18 +0,0 @@ ---- -################################################################################ -# Install Docker from Docker's official repository and Weave Net. -################################################################################ - -- name: install docker and weave net for development - hosts: all - gather_facts: false # required in case Python is not available on the host - become: true - become_user: root - - pre_tasks: - - include: library/setup_ansible_dependencies.yml - - roles: - - docker-install - - weave-net-utilities - - weave-net diff --git a/tools/config_management/setup_weave-net_dev.yml b/tools/config_management/setup_weave-net_dev.yml deleted file mode 100644 index bdfa08e9..00000000 --- a/tools/config_management/setup_weave-net_dev.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -################################################################################ -# Install Docker from Docker's official repository and Weave Net. -################################################################################ - -- name: install docker and weave net for development - hosts: all - gather_facts: false # required in case Python is not available on the host - become: true - become_user: root - - pre_tasks: - - include: library/setup_ansible_dependencies.yml - - roles: - - dev-tools - - golang-from-tarball - - docker-install - # Do not run this role when building with Vagrant, as sources have been already checked out: - - { role: weave-net-sources, when: "ansible_user != 'vagrant'" } diff --git a/tools/config_management/setup_weave-net_test.yml b/tools/config_management/setup_weave-net_test.yml deleted file mode 100644 index fbd155df..00000000 --- a/tools/config_management/setup_weave-net_test.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -################################################################################ -# Install Docker from Docker's official repository and Weave Net. -################################################################################ - -- name: install docker and weave net for testing - hosts: all - gather_facts: false # required in case Python is not available on the host - become: true - become_user: root - - pre_tasks: - - include: library/setup_ansible_dependencies.yml - - roles: - - docker-install - - weave-net-utilities - - kubernetes-install - - kubernetes-docker-images - - kubelet-stop diff --git a/tools/cover/Makefile b/tools/cover/Makefile deleted file mode 100644 index 1453e63e..00000000 --- a/tools/cover/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -.PHONY: all clean - -all: cover - -cover: *.go - go get -tags netgo ./$(@D) - go build -ldflags "-extldflags \"-static\" -linkmode=external" -tags netgo -o $@ ./$(@D) - -clean: - rm -rf cover - go clean ./... diff --git a/tools/cover/cover.go b/tools/cover/cover.go deleted file mode 100644 index 4c5fcfd7..00000000 --- a/tools/cover/cover.go +++ /dev/null @@ -1,97 +0,0 @@ -package main - -import ( - "fmt" - "os" - "sort" - - "golang.org/x/tools/cover" -) - -func merge(p1, p2 *cover.Profile) *cover.Profile { - output := cover.Profile{ - FileName: p1.FileName, - Mode: p1.Mode, - } - - i, j := 0, 0 - for i < len(p1.Blocks) && j < len(p2.Blocks) { - bi, bj := p1.Blocks[i], p2.Blocks[j] - if bi.StartLine == bj.StartLine && bi.StartCol == bj.StartCol { - - if bi.EndLine != bj.EndLine || - bi.EndCol != bj.EndCol || - bi.NumStmt != bj.NumStmt { - panic("Not run on same source!") - } - - output.Blocks = append(output.Blocks, cover.ProfileBlock{ - StartLine: bi.StartLine, - StartCol: bi.StartCol, - EndLine: bi.EndLine, - EndCol: bi.EndCol, - NumStmt: bi.NumStmt, - Count: bi.Count + bj.Count, - }) - i++ - j++ - } else if bi.StartLine < bj.StartLine || bi.StartLine == bj.StartLine && bi.StartCol < bj.StartCol { - output.Blocks = append(output.Blocks, bi) - i++ - } else { - output.Blocks = append(output.Blocks, bj) - j++ - } - } - - for ; i < len(p1.Blocks); i++ { - output.Blocks = append(output.Blocks, p1.Blocks[i]) - } - - for ; j < len(p2.Blocks); j++ { - output.Blocks = append(output.Blocks, p2.Blocks[j]) - } - - return &output -} - -func print(profiles []*cover.Profile) { - fmt.Println("mode: atomic") - for _, profile := range profiles { - for _, block := range profile.Blocks { - fmt.Printf("%s:%d.%d,%d.%d %d %d\n", profile.FileName, block.StartLine, block.StartCol, - block.EndLine, block.EndCol, block.NumStmt, block.Count) - } - } -} - -// Copied from https://github.com/golang/tools/blob/master/cover/profile.go -type byFileName []*cover.Profile - -func (p byFileName) Len() int { return len(p) } -func (p byFileName) Less(i, j int) bool { return p[i].FileName < p[j].FileName } -func (p byFileName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - -func main() { - outputProfiles := map[string]*cover.Profile{} - for _, input := range os.Args[1:] { - inputProfiles, err := cover.ParseProfiles(input) - if err != nil { - panic(fmt.Sprintf("Error parsing %s: %v", input, err)) - } - for _, ip := range inputProfiles { - op := outputProfiles[ip.FileName] - if op == nil { - outputProfiles[ip.FileName] = ip - } else { - outputProfiles[ip.FileName] = merge(op, ip) - } - } - } - profiles := make([]*cover.Profile, 0, len(outputProfiles)) - for _, profile := range outputProfiles { - profiles = append(profiles, profile) - } - sort.Sort(byFileName(profiles)) - print(profiles) -} diff --git a/tools/cover/gather_coverage.sh b/tools/cover/gather_coverage.sh deleted file mode 100755 index 271ac7d4..00000000 --- a/tools/cover/gather_coverage.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -# This scripts copies all the coverage reports from various circle shards, -# merges them and produces a complete report. - -set -ex -DESTINATION=$1 -FROMDIR=$2 -mkdir -p "$DESTINATION" - -if [ -n "$CIRCLECI" ]; then - for i in $(seq 1 $((CIRCLE_NODE_TOTAL - 1))); do - scp "node$i:$FROMDIR"/* "$DESTINATION" || true - done -fi - -go get github.com/weaveworks/build-tools/cover -cover "$DESTINATION"/* >profile.cov -go tool cover -html=profile.cov -o coverage.html -go tool cover -func=profile.cov -o coverage.txt -tar czf coverage.tar.gz "$DESTINATION" diff --git a/tools/dependencies/cross_versions.py b/tools/dependencies/cross_versions.py deleted file mode 100755 index dd920f0e..00000000 --- a/tools/dependencies/cross_versions.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python - -# Generate the cross product of latest versions of Weave Net's dependencies: -# - Go -# - Docker -# - Kubernetes -# -# Dependencies: -# - python -# - git -# - list_versions.py -# -# Testing: -# $ python -m doctest -v cross_versions.py - -from os import linesep -from sys import argv, exit, stdout, stderr -from getopt import getopt, GetoptError -from list_versions import DEPS, get_versions_from, filter_versions -from itertools import product - -# See also: /usr/include/sysexits.h -_ERROR_RUNTIME = 1 -_ERROR_ILLEGAL_ARGS = 64 - - -def _usage(error_message=None): - if error_message: - stderr.write('ERROR: ' + error_message + linesep) - stdout.write( - linesep.join([ - 'Usage:', ' cross_versions.py [OPTION]...', 'Examples:', - ' cross_versions.py', ' cross_versions.py -r', - ' cross_versions.py --rc', ' cross_versions.py -l', - ' cross_versions.py --latest', 'Options:', - '-l/--latest Include only the latest version of each major and' - ' minor versions sub-tree.', - '-r/--rc Include release candidate versions.', - '-h/--help Prints this!', '' - ])) - - -def _validate_input(argv): - try: - config = {'rc': False, 'latest': False} - opts, args = getopt(argv, 'hlr', ['help', 'latest', 'rc']) - for opt, value in opts: - if opt in ('-h', '--help'): - _usage() - exit() - if opt in ('-l', '--latest'): - config['latest'] = True - if opt in ('-r', '--rc'): - config['rc'] = True - if len(args) != 0: - raise ValueError('Unsupported argument(s): %s.' % args) - return config - except GetoptError as e: - _usage(str(e)) - exit(_ERROR_ILLEGAL_ARGS) - except ValueError as e: - _usage(str(e)) - exit(_ERROR_ILLEGAL_ARGS) - - -def _versions(dependency, config): - return map(str, - filter_versions( - get_versions_from(DEPS[dependency]['url'], - DEPS[dependency]['re']), - DEPS[dependency]['min'], **config)) - - -def cross_versions(config): - docker_versions = _versions('docker', config) - k8s_versions = _versions('kubernetes', config) - return product(docker_versions, k8s_versions) - - -def main(argv): - try: - config = _validate_input(argv) - print(linesep.join('\t'.join(triple) - for triple in cross_versions(config))) - except Exception as e: - print(str(e)) - exit(_ERROR_RUNTIME) - - -if __name__ == '__main__': - main(argv[1:]) diff --git a/tools/dependencies/list_os_images.sh b/tools/dependencies/list_os_images.sh deleted file mode 100755 index 00db0d06..00000000 --- a/tools/dependencies/list_os_images.sh +++ /dev/null @@ -1,85 +0,0 @@ -#!/bin/bash - -function usage() { - cat <&2 "No AWS owner ID for $1." - exit 1 -} - -if [ -z "$1" ]; then - echo >&2 "No specified provider." - usage - exit 1 -fi - -if [ -z "$2" ]; then - if [ "$1" == "help" ]; then - usage - exit 0 - else - echo >&2 "No specified operating system." - usage - exit 1 - fi -fi - -case "$1" in - 'gcp') - gcloud compute images list --standard-images --regexp=".*?$2.*" \ - --format="csv[no-heading][separator=/](selfLink.map().scope(projects).segment(0),family)" \ - | sort -d - ;; - 'aws') - aws --region "${3:-us-east-1}" ec2 describe-images \ - --owners "$(find_aws_owner_id "$2")" \ - --filters "Name=name,Values=$2*" \ - --query 'Images[*].{name:Name,id:ImageId}' - # Other examples: - # - CentOS: aws --region us-east-1 ec2 describe-images --owners aws-marketplace --filters Name=product-code,Values=aw0evgkw8e5c1q413zgy5pjce - # - Debian: aws --region us-east-1 ec2 describe-images --owners 379101102735 --filters "Name=architecture,Values=x86_64" "Name=name,Values=debian-jessie-*" "Name=root-device-type,Values=ebs" "Name=virtualization-type,Values=hvm" - ;; - 'do') - curl -s -X GET \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \ - "https://api.digitalocean.com/v2/images?page=1&per_page=999999" \ - | jq --raw-output ".images | .[] | .slug" | grep "$2" | sort -d - ;; - *) - echo >&2 "Unknown provider [$1]." - usage - exit 1 - ;; -esac diff --git a/tools/dependencies/list_versions.py b/tools/dependencies/list_versions.py deleted file mode 100755 index e008ecfe..00000000 --- a/tools/dependencies/list_versions.py +++ /dev/null @@ -1,343 +0,0 @@ -#!/usr/bin/env python - -# List all available versions of Weave Net's dependencies: -# - Go -# - Docker -# - Kubernetes -# -# Depending on the parameters passed, it can gather the equivalent of the below -# bash one-liners: -# git ls-remote --tags https://github.com/golang/go \ -# | grep -oP '(?<=refs/tags/go)[\.\d]+$' \ -# | sort --version-sort -# git ls-remote --tags https://github.com/golang/go \ -# | grep -oP '(?<=refs/tags/go)[\.\d]+rc\d+$' \ -# | sort --version-sort \ -# | tail -n 1 -# git ls-remote --tags https://github.com/docker/docker \ -# | grep -oP '(?<=refs/tags/v)\d+\.\d+\.\d+$' \ -# | sort --version-sort -# git ls-remote --tags https://github.com/docker/docker \ -# | grep -oP '(?<=refs/tags/v)\d+\.\d+\.\d+\-rc\d*$' \ -# | sort --version-sort \ -# | tail -n 1 -# git ls-remote --tags https://github.com/kubernetes/kubernetes \ -# | grep -oP '(?<=refs/tags/v)\d+\.\d+\.\d+$' \ -# | sort --version-sort -# git ls-remote --tags https://github.com/kubernetes/kubernetes \ -# | grep -oP '(?<=refs/tags/v)\d+\.\d+\.\d+\-beta\.\d+$' \ -# | sort --version-sort | tail -n 1 -# -# Dependencies: -# - python -# - git -# -# Testing: -# $ python -m doctest -v list_versions.py - -from os import linesep, path -from sys import argv, exit, stdout, stderr -from getopt import getopt, GetoptError -from subprocess import Popen, PIPE -from pkg_resources import parse_version -from itertools import groupby -from six.moves import filter -import shlex -import re - -# See also: /usr/include/sysexits.h -_ERROR_RUNTIME = 1 -_ERROR_ILLEGAL_ARGS = 64 - -_TAG_REGEX = '^[0-9a-f]{40}\s+refs/tags/%s$' -_VERSION = 'version' -DEPS = { - 'go': { - 'url': 'https://github.com/golang/go', - 're': 'go(?P<%s>[\d\.]+(?:rc\d)*)' % _VERSION, - 'min': None - }, - 'docker': { - 'url': 'https://github.com/docker/docker', - 're': 'v(?P<%s>\d+\.\d+\.\d+(?:\-rc\d)*)' % _VERSION, - # Weave Net only works with Docker from 1.10.0 onwards, so we ignore - # all previous versions: - 'min': '1.10.0', - }, - 'kubernetes': { - 'url': 'https://github.com/kubernetes/kubernetes', - 're': 'v(?P<%s>\d+\.\d+\.\d+(?:\-beta\.\d)*)' % _VERSION, - # Weave Kube requires Kubernetes 1.4.2+, so we ignore all previous - # versions: - 'min': '1.4.2', - } -} - - -class Version(object): - ''' Helper class to parse and manipulate (sort, filter, group) software - versions. ''' - - def __init__(self, version): - self.version = version - self.digits = [ - int(x) if x else 0 - for x in re.match('(\d*)\.?(\d*)\.?(\d*).*?', version).groups() - ] - self.major, self.minor, self.patch = self.digits - self.__parsed = parse_version(version) - self.is_rc = self.__parsed.is_prerelease - - def __lt__(self, other): - return self.__parsed.__lt__(other.__parsed) - - def __gt__(self, other): - return self.__parsed.__gt__(other.__parsed) - - def __le__(self, other): - return self.__parsed.__le__(other.__parsed) - - def __ge__(self, other): - return self.__parsed.__ge__(other.__parsed) - - def __eq__(self, other): - return self.__parsed.__eq__(other.__parsed) - - def __ne__(self, other): - return self.__parsed.__ne__(other.__parsed) - - def __str__(self): - return self.version - - def __repr__(self): - return self.version - - -def _read_go_version_from_dockerfile(): - # Read Go version from weave/build/Dockerfile - dockerfile_path = path.join( - path.dirname(path.dirname(path.dirname(path.realpath(__file__)))), - 'build', 'Dockerfile') - with open(dockerfile_path, 'r') as f: - for line in f: - m = re.match('^FROM golang:(\S*)$', line) - if m: - return m.group(1) - raise RuntimeError( - "Failed to read Go version from weave/build/Dockerfile." - " You may be running this script from somewhere else than weave/tools." - ) - - -def _try_set_min_go_version(): - ''' Set the current version of Go used to build Weave Net's containers as - the minimum version. ''' - try: - DEPS['go']['min'] = _read_go_version_from_dockerfile() - except IOError as e: - stderr.write('WARNING: No minimum Go version set. Root cause: %s%s' % - (e, linesep)) - - -def _sanitize(out): - return out.decode('ascii').strip().split(linesep) - - -def _parse_tag(tag, version_pattern, debug=False): - ''' Parse Git tag output's line using the provided `version_pattern`, e.g.: - >>> _parse_tag( - '915b77eb4efd68916427caf8c7f0b53218c5ea4a refs/tags/v1.4.6', - 'v(?P\d+\.\d+\.\d+(?:\-beta\.\d)*)') - '1.4.6' - ''' - pattern = _TAG_REGEX % version_pattern - m = re.match(pattern, tag) - if m: - return m.group(_VERSION) - elif debug: - stderr.write( - 'ERROR: Failed to parse version out of tag [%s] using [%s].%s' % - (tag, pattern, linesep)) - - -def get_versions_from(git_repo_url, version_pattern): - ''' Get release and release candidates' versions from the provided Git - repository. ''' - git = Popen( - shlex.split('git ls-remote --tags %s' % git_repo_url), stdout=PIPE) - out, err = git.communicate() - status_code = git.returncode - if status_code != 0: - raise RuntimeError('Failed to retrieve git tags from %s. ' - 'Status code: %s. Output: %s. Error: %s' % - (git_repo_url, status_code, out, err)) - return list( - filter(None, (_parse_tag(line, version_pattern) - for line in _sanitize(out)))) - - -def _tree(versions, level=0): - ''' Group versions by major, minor and patch version digits. ''' - if not versions or level >= len(versions[0].digits): - return # Empty versions or no more digits to group by. - versions_tree = [] - for _, versions_group in groupby(versions, lambda v: v.digits[level]): - subtree = _tree(list(versions_group), level + 1) - if subtree: - versions_tree.append(subtree) - # Return the current subtree if non-empty, or the list of "leaf" versions: - return versions_tree if versions_tree else versions - - -def _is_iterable(obj): - ''' - Check if the provided object is an iterable collection, i.e. not a string, - e.g. a list, a generator: - >>> _is_iterable('string') - False - >>> _is_iterable([1, 2, 3]) - True - >>> _is_iterable((x for x in [1, 2, 3])) - True - ''' - return hasattr(obj, '__iter__') and not isinstance(obj, str) - - -def _leaf_versions(tree, rc): - ''' - Recursively traverse the versions tree in a depth-first fashion, - and collect the last node of each branch, i.e. leaf versions. - ''' - versions = [] - if _is_iterable(tree): - for subtree in tree: - versions.extend(_leaf_versions(subtree, rc)) - if not versions: - if rc: - last_rc = next(filter(lambda v: v.is_rc, reversed(tree)), None) - last_prod = next( - filter(lambda v: not v.is_rc, reversed(tree)), None) - if last_rc and last_prod and (last_prod < last_rc): - versions.extend([last_prod, last_rc]) - elif not last_prod: - versions.append(last_rc) - else: - # Either there is no RC, or we ignore the RC as older than - # the latest production version: - versions.append(last_prod) - else: - versions.append(tree[-1]) - return versions - - -def filter_versions(versions, min_version=None, rc=False, latest=False): - ''' Filter provided versions - - >>> filter_versions( - ['1.0.0-beta.1', '1.0.0', '1.0.1', '1.1.1', '1.1.2-rc1', '2.0.0'], - min_version=None, latest=False, rc=False) - [1.0.0, 1.0.1, 1.1.1, 2.0.0] - - >>> filter_versions( - ['1.0.0-beta.1', '1.0.0', '1.0.1', '1.1.1', '1.1.2-rc1', '2.0.0'], - min_version=None, latest=True, rc=False) - [1.0.1, 1.1.1, 2.0.0] - - >>> filter_versions( - ['1.0.0-beta.1', '1.0.0', '1.0.1', '1.1.1', '1.1.2-rc1', '2.0.0'], - min_version=None, latest=False, rc=True) - [1.0.0-beta.1, 1.0.0, 1.0.1, 1.1.1, 1.1.2-rc1, 2.0.0] - - >>> filter_versions( - ['1.0.0-beta.1', '1.0.0', '1.0.1', '1.1.1', '1.1.2-rc1', '2.0.0'], - min_version='1.1.0', latest=False, rc=True) - [1.1.1, 1.1.2-rc1, 2.0.0] - - >>> filter_versions( - ['1.0.0-beta.1', '1.0.0', '1.0.1', '1.1.1', '1.1.2-rc1', '2.0.0'], - min_version=None, latest=True, rc=True) - [1.0.1, 1.1.1, 1.1.2-rc1, 2.0.0] - - >>> filter_versions( - ['1.0.0-beta.1', '1.0.0', '1.0.1', '1.1.1', '1.1.2-rc1', '2.0.0'], - min_version='1.1.0', latest=True, rc=True) - [1.1.1, 1.1.2-rc1, 2.0.0] - ''' - versions = sorted([Version(v) for v in versions]) - if min_version: - min_version = Version(min_version) - versions = [v for v in versions if v >= min_version] - if not rc: - versions = [v for v in versions if not v.is_rc] - if latest: - versions_tree = _tree(versions) - return _leaf_versions(versions_tree, rc) - else: - return versions - - -def _usage(error_message=None): - if error_message: - stderr.write('ERROR: ' + error_message + linesep) - stdout.write( - linesep.join([ - 'Usage:', ' list_versions.py [OPTION]... [DEPENDENCY]', - 'Examples:', ' list_versions.py go', - ' list_versions.py -r docker', - ' list_versions.py --rc docker', - ' list_versions.py -l kubernetes', - ' list_versions.py --latest kubernetes', 'Options:', - '-l/--latest Include only the latest version of each major and' - ' minor versions sub-tree.', - '-r/--rc Include release candidate versions.', - '-h/--help Prints this!', '' - ])) - - -def _validate_input(argv): - try: - config = {'rc': False, 'latest': False} - opts, args = getopt(argv, 'hlr', ['help', 'latest', 'rc']) - for opt, value in opts: - if opt in ('-h', '--help'): - _usage() - exit() - if opt in ('-l', '--latest'): - config['latest'] = True - if opt in ('-r', '--rc'): - config['rc'] = True - if len(args) != 1: - raise ValueError('Please provide a dependency to get versions of.' - ' Expected 1 argument but got %s: %s.' % - (len(args), args)) - dependency = args[0].lower() - if dependency not in DEPS.keys(): - raise ValueError( - 'Please provide a valid dependency.' - ' Supported one dependency among {%s} but got: %s.' % - (', '.join(DEPS.keys()), dependency)) - return dependency, config - except GetoptError as e: - _usage(str(e)) - exit(_ERROR_ILLEGAL_ARGS) - except ValueError as e: - _usage(str(e)) - exit(_ERROR_ILLEGAL_ARGS) - - -def main(argv): - try: - dependency, config = _validate_input(argv) - if dependency == 'go': - _try_set_min_go_version() - versions = get_versions_from(DEPS[dependency]['url'], - DEPS[dependency]['re']) - versions = filter_versions(versions, DEPS[dependency]['min'], **config) - print(linesep.join(map(str, versions))) - except Exception as e: - print(str(e)) - exit(_ERROR_RUNTIME) - - -if __name__ == '__main__': - main(argv[1:]) diff --git a/tools/files-with-type b/tools/files-with-type deleted file mode 100755 index 8238980c..00000000 --- a/tools/files-with-type +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# -# Find all files with a given MIME type. -# -# e.g. -# $ files-with-type text/x-shellscript k8s infra - -mime_type=$1 -shift - -git ls-files "$@" | grep -vE '^vendor/' | xargs file --mime-type | grep "${mime_type}" | sed -e 's/:.*$//' diff --git a/tools/image-tag b/tools/image-tag deleted file mode 100755 index 31f023da..00000000 --- a/tools/image-tag +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -WORKING_SUFFIX=$(if git status --porcelain | grep -qE '^(?:[^?][^ ]|[^ ][^?])\s'; then echo "-WIP"; else echo ""; fi) -BRANCH_PREFIX=$(git rev-parse --abbrev-ref HEAD) -echo "${BRANCH_PREFIX//\//-}-$(git rev-parse --short HEAD)$WORKING_SUFFIX" diff --git a/tools/integration/assert.sh b/tools/integration/assert.sh deleted file mode 100755 index 200b393b..00000000 --- a/tools/integration/assert.sh +++ /dev/null @@ -1,193 +0,0 @@ -#!/bin/bash -# assert.sh 1.1 - bash unit testing framework -# Copyright (C) 2009-2015 Robert Lehmann -# -# http://github.com/lehmannro/assert.sh -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . - -export DISCOVERONLY=${DISCOVERONLY:-} -export DEBUG=${DEBUG:-} -export STOP=${STOP:-} -export INVARIANT=${INVARIANT:-} -export CONTINUE=${CONTINUE:-} - -args="$(getopt -n "$0" -l \ - verbose,help,stop,discover,invariant,continue vhxdic "$@")" \ - || exit -1 -for arg in $args; do - case "$arg" in - -h) - echo "$0 [-vxidc]" \ - "[--verbose] [--stop] [--invariant] [--discover] [--continue]" - echo "$(sed 's/./ /g' <<<"$0") [-h] [--help]" - exit 0 - ;; - --help) - cat < [stdin] - ((tests_ran++)) || : - [[ -z "$DISCOVERONLY" ]] || return - expected=$(echo -ne "${2:-}") - result="$(eval "$1" 2>/dev/null <<<"${3:-}")" || true - if [[ "$result" == "$expected" ]]; then - [[ -z "$DEBUG" ]] || echo -n . - return - fi - result="$(sed -e :a -e '$!N;s/\n/\\n/;ta' <<<"$result")" - [[ -z "$result" ]] && result="nothing" || result="\"$result\"" - [[ -z "$2" ]] && expected="nothing" || expected="\"$2\"" - _assert_fail "expected $expected${_indent}got $result" "$1" "$3" -} - -assert_raises() { - # assert_raises [stdin] - ((tests_ran++)) || : - [[ -z "$DISCOVERONLY" ]] || return - status=0 - (eval "$1" <<<"${3:-}") >/dev/null 2>&1 || status=$? - expected=${2:-0} - if [[ "$status" -eq "$expected" ]]; then - [[ -z "$DEBUG" ]] || echo -n . - return - fi - _assert_fail "program terminated with code $status instead of $expected" "$1" "$3" -} - -_assert_fail() { - # _assert_fail - [[ -n "$DEBUG" ]] && echo -n X - report="test #$tests_ran \"$2${3:+ <<< $3}\" failed:${_indent}$1" - if [[ -n "$STOP" ]]; then - [[ -n "$DEBUG" ]] && echo - echo "$report" - exit 1 - fi - tests_errors[$tests_failed]="$report" - ((tests_failed++)) || : -} - -skip_if() { - # skip_if - (eval "$@") >/dev/null 2>&1 && status=0 || status=$? - [[ "$status" -eq 0 ]] || return - skip -} - -skip() { - # skip (no arguments) - shopt -q extdebug && tests_extdebug=0 || tests_extdebug=1 - shopt -q -o errexit && tests_errexit=0 || tests_errexit=1 - # enable extdebug so returning 1 in a DEBUG trap handler skips next command - shopt -s extdebug - # disable errexit (set -e) so we can safely return 1 without causing exit - set +o errexit - tests_trapped=0 - trap _skip DEBUG -} -_skip() { - if [[ $tests_trapped -eq 0 ]]; then - # DEBUG trap for command we want to skip. Do not remove the handler - # yet because *after* the command we need to reset extdebug/errexit (in - # another DEBUG trap.) - tests_trapped=1 - [[ -z "$DEBUG" ]] || echo -n s - return 1 - else - trap - DEBUG - [[ $tests_extdebug -eq 0 ]] || shopt -u extdebug - [[ $tests_errexit -eq 1 ]] || set -o errexit - return 0 - fi -} - -_assert_reset -: ${tests_suite_status:=0} # remember if any of the tests failed so far -_assert_cleanup() { - local status=$? - # modify exit code if it's not already non-zero - [[ $status -eq 0 && -z $CONTINUE ]] && exit $tests_suite_status -} -trap _assert_cleanup EXIT diff --git a/tools/integration/config.sh b/tools/integration/config.sh deleted file mode 100644 index 54192192..00000000 --- a/tools/integration/config.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/bash -# NB only to be sourced - -set -e - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - -# Protect against being sourced multiple times to prevent -# overwriting assert.sh global state -if ! [ -z "$SOURCED_CONFIG_SH" ]; then - return -fi -SOURCED_CONFIG_SH=true - -# these ought to match what is in Vagrantfile -N_MACHINES=${N_MACHINES:-3} -IP_PREFIX=${IP_PREFIX:-192.168.48} -IP_SUFFIX_BASE=${IP_SUFFIX_BASE:-10} - -if [ -z "$HOSTS" ]; then - for i in $(seq 1 "$N_MACHINES"); do - IP="${IP_PREFIX}.$((IP_SUFFIX_BASE + i))" - HOSTS="$HOSTS $IP" - done -fi - -# these are used by the tests -# shellcheck disable=SC2034 -HOST1=$(echo "$HOSTS" | cut -f 1 -d ' ') -# shellcheck disable=SC2034 -HOST2=$(echo "$HOSTS" | cut -f 2 -d ' ') -# shellcheck disable=SC2034 -HOST3=$(echo "$HOSTS" | cut -f 3 -d ' ') - -# shellcheck disable=SC1090 -. "$DIR/assert.sh" - -SSH_DIR=${SSH_DIR:-$DIR} -SSH=${SSH:-ssh -l vagrant -i \"$SSH_DIR/insecure_private_key\" -o \"UserKnownHostsFile=$SSH_DIR/.ssh_known_hosts\" -o CheckHostIP=no -o StrictHostKeyChecking=no} - -SMALL_IMAGE="alpine" -# shellcheck disable=SC2034 -TEST_IMAGES="$SMALL_IMAGE" - -# shellcheck disable=SC2034 -PING="ping -nq -W 1 -c 1" -DOCKER_PORT=2375 - -remote() { - rem=$1 - shift 1 - "$@" > >(while read -r line; do echo -e $'\e[0;34m'"$rem>"$'\e[0m'" $line"; done) -} - -colourise() { - ([ -t 0 ] && echo -ne $'\e['"$1"'m') || true - shift - # It's important that we don't do this in a subshell, as some - # commands we execute need to modify global state - "$@" - ([ -t 0 ] && echo -ne $'\e[0m') || true -} - -whitely() { - colourise '1;37' "$@" -} - -greyly() { - colourise '0;37' "$@" -} - -redly() { - colourise '1;31' "$@" -} - -greenly() { - colourise '1;32' "$@" -} - -run_on() { - host=$1 - shift 1 - [ -z "$DEBUG" ] || greyly echo "Running on $host:" "$@" >&2 - # shellcheck disable=SC2086 - remote "$host" $SSH "$host" "$@" -} - -docker_on() { - host=$1 - shift 1 - [ -z "$DEBUG" ] || greyly echo "Docker on $host:$DOCKER_PORT:" "$@" >&2 - docker -H "tcp://$host:$DOCKER_PORT" "$@" -} - -weave_on() { - host=$1 - shift 1 - [ -z "$DEBUG" ] || greyly echo "Weave on $host:$DOCKER_PORT:" "$@" >&2 - DOCKER_HOST=tcp://$host:$DOCKER_PORT $WEAVE "$@" -} - -exec_on() { - host=$1 - container=$2 - shift 2 - docker -H "tcp://$host:$DOCKER_PORT" exec "$container" "$@" -} - -rm_containers() { - host=$1 - shift - [ $# -eq 0 ] || docker_on "$host" rm -f "$@" >/dev/null -} - -start_suite() { - for host in $HOSTS; do - [ -z "$DEBUG" ] || echo "Cleaning up on $host: removing all containers and resetting weave" - # shellcheck disable=SC2046 - rm_containers "$host" $(docker_on "$host" ps -aq 2>/dev/null) - run_on "$host" "docker network ls | grep -q ' weave ' && docker network rm weave" || true - weave_on "$host" reset 2>/dev/null - done - whitely echo "$@" -} - -end_suite() { - whitely assert_end -} - -WEAVE=$DIR/../../integration/weave diff --git a/tools/integration/gce.sh b/tools/integration/gce.sh deleted file mode 100755 index 5c394018..00000000 --- a/tools/integration/gce.sh +++ /dev/null @@ -1,202 +0,0 @@ -#!/bin/bash -# This script has a bunch of GCE-related functions: -# ./gce.sh setup - starts two VMs on GCE and configures them to run our integration tests -# . ./gce.sh; ./run_all.sh - set a bunch of environment variables for the tests -# ./gce.sh destroy - tear down the VMs -# ./gce.sh make_template - make a fresh VM template; update TEMPLATE_NAME first! - -set -e - -: "${KEY_FILE:=/tmp/gce_private_key.json}" -: "${SSH_KEY_FILE:=$HOME/.ssh/gce_ssh_key}" -: "${IMAGE_FAMILY:=ubuntu-1404-lts}" -: "${IMAGE_PROJECT:=ubuntu-os-cloud}" -: "${USER_ACCOUNT:=ubuntu}" -: "${ZONE:=us-central1-a}" -: "${PROJECT:=}" -: "${TEMPLATE_NAME:=}" -: "${NUM_HOSTS:=}" - -if [ -z "${PROJECT}" ] || [ -z "${NUM_HOSTS}" ] || [ -z "${TEMPLATE_NAME}" ]; then - echo "Must specify PROJECT, NUM_HOSTS and TEMPLATE_NAME" - exit 1 -fi - -SUFFIX="" -if [ -n "$CIRCLECI" ]; then - SUFFIX="-${CIRCLE_PROJECT_USERNAME}-${CIRCLE_PROJECT_REPONAME}-${CIRCLE_BUILD_NUM}-$CIRCLE_NODE_INDEX" -else - SUFFIX="-${USER}" -fi - -# Setup authentication -gcloud auth activate-service-account --key-file "$KEY_FILE" 1>/dev/null -gcloud config set project "$PROJECT" - -function vm_names() { - local names= - for i in $(seq 1 "$NUM_HOSTS"); do - names=("host$i$SUFFIX" "${names[@]}") - done - echo "${names[@]}" -} - -# Delete all vms in this account -function destroy() { - local names - # shellcheck disable=SC2046 - if [ $(gcloud compute firewall-rules list "test-allow-docker$SUFFIX" 2>/dev/null | wc -l) -gt 0 ]; then - gcloud compute firewall-rules delete "test-allow-docker$SUFFIX" - fi - names="$(vm_names)" - # shellcheck disable=SC2086 - if [ "$(gcloud compute instances list --zones "$ZONE" -q $names | wc -l)" -le 1 ]; then - return 0 - fi - for i in {0..10}; do - # gcloud instances delete can sometimes hang. - case $( - set +e - timeout 60s /bin/bash -c "gcloud compute instances delete --zone $ZONE -q $names >/dev/null 2>&1" - echo $? - ) in - 0) - return 0 - ;; - 124) - # 124 means it timed out - break - ;; - *) - return 1 - ;; - esac - done -} - -function internal_ip() { - jq -r ".[] | select(.name == \"$2\") | .networkInterfaces[0].networkIP" "$1" -} - -function external_ip() { - jq -r ".[] | select(.name == \"$2\") | .networkInterfaces[0].accessConfigs[0].natIP" "$1" -} - -function try_connect() { - for i in {0..10}; do - ssh -t "$1" true && return - sleep 2 - done -} - -function install_docker_on() { - name=$1 - echo "Installing Docker on $name for user ${USER_ACCOUNT}" - # shellcheck disable=SC2087 - ssh -t "$name" sudo bash -x -s <> /etc/default/docker; -service docker restart -EOF - # It seems we need a short delay for docker to start up, so I put this in - # a separate ssh connection. This installs nsenter. - ssh -t "$name" sudo docker run --rm -v /usr/local/bin:/target jpetazzo/nsenter -} - -function copy_hosts() { - hostname=$1 - hosts=$2 - ssh -t "$hostname" "sudo -- sh -c \"cat >>/etc/hosts\"" <"$hosts" -} - -# Create new set of VMs -function setup() { - destroy - - names=($(vm_names)) - gcloud compute instances create "${names[@]}" --image "$TEMPLATE_NAME" --zone "$ZONE" --tags "test$SUFFIX" --network=test - my_ip="$(curl -s http://ipinfo.io/ip)" - gcloud compute firewall-rules create "test-allow-docker$SUFFIX" --network=test --allow tcp:2375,tcp:12375,tcp:4040,tcp:80 --target-tags "test$SUFFIX" --source-ranges "$my_ip" - - gcloud compute config-ssh --ssh-key-file "$SSH_KEY_FILE" - sed -i '/UserKnownHostsFile=\/dev\/null/d' ~/.ssh/config - - # build an /etc/hosts file for these vms - hosts=$(mktemp hosts.XXXXXXXXXX) - json=$(mktemp json.XXXXXXXXXX) - gcloud compute instances list --format=json >"$json" - for name in "${names[@]}"; do - echo "$(internal_ip "$json" "$name") $name.$ZONE.$PROJECT" >>"$hosts" - done - - for name in "${names[@]}"; do - hostname="$name.$ZONE.$PROJECT" - - # Add the remote ip to the local /etc/hosts - sudo sed -i "/$hostname/d" /etc/hosts - sudo sh -c "echo \"$(external_ip "$json" "$name") $hostname\" >>/etc/hosts" - try_connect "$hostname" - - copy_hosts "$hostname" "$hosts" & - done - - wait - - rm "$hosts" "$json" -} - -function make_template() { - gcloud compute instances create "$TEMPLATE_NAME" --image-family "$IMAGE_FAMILY" --image-project "$IMAGE_PROJECT" --zone "$ZONE" - gcloud compute config-ssh --ssh-key-file "$SSH_KEY_FILE" - name="$TEMPLATE_NAME.$ZONE.$PROJECT" - try_connect "$name" - install_docker_on "$name" - gcloud -q compute instances delete "$TEMPLATE_NAME" --keep-disks boot --zone "$ZONE" - gcloud compute images create "$TEMPLATE_NAME" --source-disk "$TEMPLATE_NAME" --source-disk-zone "$ZONE" -} - -function hosts() { - hosts= - args= - json=$(mktemp json.XXXXXXXXXX) - gcloud compute instances list --format=json >"$json" - for name in $(vm_names); do - hostname="$name.$ZONE.$PROJECT" - hosts=($hostname "${hosts[@]}") - args=("--add-host=$hostname:$(internal_ip "$json" "$name")" "${args[@]}") - done - echo export SSH=\"ssh -l "${USER_ACCOUNT}"\" - echo "export HOSTS=\"${hosts[*]}\"" - echo "export ADD_HOST_ARGS=\"${args[*]}\"" - rm "$json" -} - -case "$1" in - setup) - setup - ;; - - hosts) - hosts - ;; - - destroy) - destroy - ;; - - make_template) - # see if template exists - if ! gcloud compute images list | grep "$PROJECT" | grep "$TEMPLATE_NAME"; then - make_template - else - echo "Reusing existing template:" - gcloud compute images describe "$TEMPLATE_NAME" | grep "^creationTimestamp" - fi - ;; -esac diff --git a/tools/integration/run_all.sh b/tools/integration/run_all.sh deleted file mode 100755 index 837e0d49..00000000 --- a/tools/integration/run_all.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/bash - -set -ex - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -# shellcheck disable=SC1090 -. "$DIR/config.sh" - -whitely echo Sanity checks -if ! bash "$DIR/sanity_check.sh"; then - whitely echo ...failed - exit 1 -fi -whitely echo ...ok - -# shellcheck disable=SC2068 -TESTS=(${@:-$(find . -name '*_test.sh')}) -RUNNER_ARGS=() - -# If running on circle, use the scheduler to work out what tests to run -if [ -n "$CIRCLECI" ] && [ -z "$NO_SCHEDULER" ]; then - RUNNER_ARGS=("${RUNNER_ARGS[@]}" -scheduler) -fi - -# If running on circle or PARALLEL is not empty, run tests in parallel -if [ -n "$CIRCLECI" ] || [ -n "$PARALLEL" ]; then - RUNNER_ARGS=("${RUNNER_ARGS[@]}" -parallel) -fi - -make -C "${DIR}/../runner" -HOSTS="$HOSTS" "${DIR}/../runner/runner" "${RUNNER_ARGS[@]}" "${TESTS[@]}" diff --git a/tools/integration/sanity_check.sh b/tools/integration/sanity_check.sh deleted file mode 100755 index 192112de..00000000 --- a/tools/integration/sanity_check.sh +++ /dev/null @@ -1,26 +0,0 @@ -#! /bin/bash -# shellcheck disable=SC1090,SC1091 -. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config.sh" - -set -e - -whitely echo Ping each host from the other -for host in $HOSTS; do - for other in $HOSTS; do - [ "$host" = "$other" ] || run_on "$host" "$PING" "$other" - done -done - -whitely echo Check we can reach docker - -for host in $HOSTS; do - echo - echo "Host Version Info: $host" - echo "=====================================" - echo "# docker version" - docker_on "$host" version - echo "# docker info" - docker_on "$host" info - echo "# weave version" - weave_on "$host" version -done diff --git a/tools/lint b/tools/lint deleted file mode 100755 index 63c50661..00000000 --- a/tools/lint +++ /dev/null @@ -1,255 +0,0 @@ -#!/bin/bash -# This scipt lints files for common errors. -# -# For go files, it runs gofmt and go vet, and optionally golint and -# gocyclo, if they are installed. -# -# For shell files, it runs shfmt. If you don't have that installed, you can get -# it with: -# go get -u gopkg.in/mvdan/sh.v1/cmd/shfmt -# -# With no arguments, it lints the current files staged -# for git commit. Or you can pass it explicit filenames -# (or directories) and it will lint them. -# -# To use this script automatically, run: -# ln -s ../../bin/lint .git/hooks/pre-commit - -set -e - -LINT_IGNORE_FILE=${LINT_IGNORE_FILE:-".lintignore"} - -IGNORE_LINT_COMMENT= -IGNORE_SPELLINGS= -PARALLEL= -while true; do - case "$1" in - -nocomment) - IGNORE_LINT_COMMENT=1 - shift 1 - ;; - -notestpackage) - # NOOP, still accepted for backwards compatibility - shift 1 - ;; - -ignorespelling) - IGNORE_SPELLINGS="$2,$IGNORE_SPELLINGS" - shift 2 - ;; - -p) - PARALLEL=1 - shift 1 - ;; - *) - break - ;; - esac -done - -spell_check() { - local filename="$1" - local lint_result=0 - - # we don't want to spell check tar balls, binaries, Makefile and json files - if file "$filename" | grep executable >/dev/null 2>&1; then - return $lint_result - fi - if [[ $filename == *".tar" || $filename == *".gz" || $filename == *".json" || $(basename "$filename") == "Makefile" ]]; then - return $lint_result - fi - - # misspell is completely optional. If you don't like it - # don't have it installed. - if ! type misspell >/dev/null 2>&1; then - return $lint_result - fi - - if ! misspell -error -i "$IGNORE_SPELLINGS" "${filename}"; then - lint_result=1 - fi - - return $lint_result -} - -lint_go() { - local filename="$1" - local lint_result=0 - - if [ -n "$(gofmt -s -l "${filename}")" ]; then - lint_result=1 - echo "${filename}: run gofmt -s -w ${filename}" - fi - - go tool vet "${filename}" || lint_result=$? - - # golint is completely optional. If you don't like it - # don't have it installed. - if type golint >/dev/null 2>&1; then - # golint doesn't set an exit code it seems - if [ -z "$IGNORE_LINT_COMMENT" ]; then - lintoutput=$(golint "${filename}") - else - lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports|ALL_CAPS') - fi - if [ -n "$lintoutput" ]; then - lint_result=1 - echo "$lintoutput" - fi - fi - - # gocyclo is completely optional. If you don't like it - # don't have it installed. Also never blocks a commit, - # it just warns. - if type gocyclo >/dev/null 2>&1; then - gocyclo -over 25 "${filename}" | while read -r line; do - echo "${filename}": higher than 25 cyclomatic complexity - "${line}" - done - fi - - return $lint_result -} - -lint_sh() { - local filename="$1" - local lint_result=0 - - if ! diff -u "${filename}" <(shfmt -i 4 "${filename}"); then - lint_result=1 - echo "${filename}: run shfmt -i 4 -w ${filename}" - fi - - # the shellcheck is completely optional. If you don't like it - # don't have it installed. - if type shellcheck >/dev/null 2>&1; then - shellcheck "${filename}" || lint_result=1 - fi - - return $lint_result -} - -lint_tf() { - local filename="$1" - local lint_result=0 - - if ! diff -u <(hclfmt "${filename}") "${filename}"; then - lint_result=1 - echo "${filename}: run hclfmt -w ${filename}" - fi - - return $lint_result -} - -lint_md() { - local filename="$1" - local lint_result=0 - - for i in '=======' '>>>>>>>'; do - if grep -q "${i}" "${filename}"; then - lint_result=1 - echo "${filename}: bad merge/rebase!" - fi - done - - return $lint_result -} - -lint_py() { - local filename="$1" - local lint_result=0 - - if yapf --diff "${filename}" | grep -qE '^[+-]'; then - lint_result=1 - echo "${filename}: run yapf --in-place ${filename}" - else - # Only run flake8 if yapf passes, since they pick up a lot of similar issues - flake8 "${filename}" || lint_result=1 - fi - - return $lint_result -} - -lint() { - filename="$1" - ext="${filename##*\.}" - local lint_result=0 - - # Don't lint deleted files - if [ ! -f "$filename" ]; then - return - fi - - # Don't lint specific files - case "$(basename "${filename}")" in - static.go) return ;; - coverage.html) return ;; - *.pb.go) return ;; - esac - - if [[ "$(file --mime-type "${filename}" | awk '{print $2}')" == "text/x-shellscript" ]]; then - ext="sh" - fi - - case "$ext" in - go) lint_go "${filename}" || lint_result=1 ;; - sh) lint_sh "${filename}" || lint_result=1 ;; - tf) lint_tf "${filename}" || lint_result=1 ;; - md) lint_md "${filename}" || lint_result=1 ;; - py) lint_py "${filename}" || lint_result=1 ;; - esac - - spell_check "${filename}" || lint_result=1 - - return $lint_result -} - -lint_files() { - local lint_result=0 - while read -r filename; do - lint "${filename}" || lint_result=1 - done - exit $lint_result -} - -matches_any() { - local filename="$1" - local patterns="$2" - while read -r pattern; do - # shellcheck disable=SC2053 - # Use the [[ operator without quotes on $pattern - # in order to "glob" the provided filename: - [[ "$filename" == $pattern ]] && return 0 - done <<<"$patterns" - return 1 -} - -filter_out() { - local patterns_file="$1" - if [ -n "$patterns_file" ] && [ -r "$patterns_file" ]; then - local patterns - patterns=$(sed '/^#.*$/d ; /^\s*$/d' "$patterns_file") # Remove blank lines and comments before we start iterating. - [ -n "$DEBUG" ] && echo >&2 "> Filters:" && echo >&2 "$patterns" - local filtered_out=() - while read -r filename; do - matches_any "$filename" "$patterns" && filtered_out+=("$filename") || echo "$filename" - done - [ -n "$DEBUG" ] && echo >&2 "> Files filtered out (i.e. NOT linted):" && printf >&2 '%s\n' "${filtered_out[@]}" - else - cat # No patterns provided: simply propagate stdin to stdout. - fi -} - -list_files() { - if [ $# -gt 0 ]; then - find "$@" | grep -vE '(^|/)vendor/' - else - git ls-files --exclude-standard | grep -vE '(^|/)vendor/' - fi -} - -if [ $# = 1 ] && [ -f "$1" ]; then - lint "$1" -elif [ -n "$PARALLEL" ]; then - list_files "$@" | filter_out "$LINT_IGNORE_FILE" | xargs -n1 -P16 "$0" -else - list_files "$@" | filter_out "$LINT_IGNORE_FILE" | lint_files -fi diff --git a/tools/provisioning/README.md b/tools/provisioning/README.md deleted file mode 100755 index 627bb42e..00000000 --- a/tools/provisioning/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Weaveworks provisioning - -## Introduction - -This project allows you to get hold of some machine either locally or on one of the below cloud providers: - -* Amazon Web Services -* Digital Ocean -* Google Cloud Platform - -You can then use these machines as is or run various Ansible playbooks from `../config_management` to set up Weave Net, Kubernetes, etc. - -## Set up - -* You will need [Vagrant](https://www.vagrantup.com) installed on your machine and added to your `PATH` in order to be able to provision local (virtual) machines automatically. - - * On macOS: `brew install vagrant` - * On Linux (via Aptitude): `sudo apt install vagrant` - * If you need a specific version: - - curl -fsS https://releases.hashicorp.com/terraform/x.y.z/terraform_x.y.z_linux_amd64.zip | gunzip > terraform && chmod +x terraform && sudo mv terraform /usr/bin - - * For other platforms or more details, see [here](https://www.vagrantup.com/docs/installation/) - -* You will need [Terraform](https://www.terraform.io) installed on your machine and added to your `PATH` in order to be able to provision cloud-hosted machines automatically. - - * On macOS: `brew install terraform` - * On Linux (via Aptitude): `sudo apt install terraform` - * For other platforms or more details, see [here](https://www.terraform.io/intro/getting-started/install.html) - -* Depending on the cloud provider, you may have to create an account, manually onboard, create and register SSH keys, etc. - Please refer to the `README.md` in each sub-folder for more details. - -## Usage in scripts - -Source `setup.sh`, set the `SECRET_KEY` environment variable, and depending on the cloud provider you want to use, call either: - -* `gcp_on` / `gcp_off` -* `do_on` / `do_off` -* `aws_on` / `aws_off` - -## Usage in shell - -Source `setup.sh`, set the `SECRET_KEY` environment variable, and depending on the cloud provider you want to use, call either: - -* `gcp_on` / `gcp_off` -* `do_on` / `do_off` -* `aws_on` / `aws_off` - -Indeed, the functions defined in `setup.sh` are also exported as aliases, so you can call them from your shell directly. - -Other aliases are also defined, in order to make your life easier: - -* `tf_ssh`: to ease SSH-ing into the virtual machines, reading the username and IP address to use from Terraform, as well as setting default SSH options. -* `tf_ansi`: to ease applying an Ansible playbook to a set of virtual machines, dynamically creating the inventory, as well as setting default SSH options. diff --git a/tools/provisioning/aws/README.md b/tools/provisioning/aws/README.md deleted file mode 100644 index f4f018f9..00000000 --- a/tools/provisioning/aws/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Amazon Web Services - -## Introduction - -This project allows you to get hold of some machine on Amazon Web Services. -You can then use these machines as is or run various Ansible playbooks from `../config_management` to set up Weave Net, Kubernetes, etc. - -## Setup - -* Log in [weaveworks.signin.aws.amazon.com/console](https://weaveworks.signin.aws.amazon.com/console/) with your account. - -* Go to `Services` > `IAM` > `Users` > Click on your username > `Security credentials` > `Create access key`. - Your access key and secret key will appear on the screen. Set these as environment variables: - -``` -export AWS_ACCESS_KEY_ID= -export AWS_SECRET_ACCESS_KEY= -``` - -* Go to `Services` > `EC2` > Select the availability zone you want to use (see top right corner, e.g. `us-east-1`) > `Import Key Pair`. - Enter your SSH public key and the name for it, and click `Import`. - Set the path to your private key as an environment variable: - -``` -export TF_VAR_aws_public_key_name= -export TF_VAR_aws_private_key_path="$HOME/.ssh/id_rsa" -``` - -* Set your current IP address as an environment variable: - -``` -export TF_VAR_client_ip=$(curl -s -X GET http://checkip.amazonaws.com/) -``` - - or pass it as a Terraform variable: - -``` -$ terraform -var 'client_ip=$(curl -s -X GET http://checkip.amazonaws.com/)' -``` - -### Bash aliases - -You can set the above variables temporarily in your current shell, permanently in your `~/.bashrc` file, or define aliases to activate/deactivate them at will with one single command by adding the below to your `~/.bashrc` file: - -``` -function _aws_on() { - export AWS_ACCESS_KEY_ID="" # Replace with appropriate value. - export AWS_SECRET_ACCESS_KEY="" # Replace with appropriate value. - export TF_VAR_aws_public_key_name="" # Replace with appropriate value. - export TF_VAR_aws_private_key_path="$HOME/.ssh/id_rsa" # Replace with appropriate value. -} -alias _aws_on='_aws_on' -function _aws_off() { - unset AWS_ACCESS_KEY_ID - unset AWS_SECRET_ACCESS_KEY - unset TF_VAR_aws_public_key_name - unset TF_VAR_aws_private_key_path -} -alias _aws_off='_aws_off' -``` - -N.B.: - -* sourcing `../setup.sh` defines aliases called `aws_on` and `aws_off`, similarly to the above (however, notice no `_` in front of the name, as opposed to the ones above); -* `../setup.sh`'s `aws_on` alias needs the `SECRET_KEY` environment variable to be set in order to decrypt sensitive information. - -## Usage - -* Create the machine: `terraform apply` -* Show the machine's status: `terraform show` -* Stop and destroy the machine: `terraform destroy` -* SSH into the newly-created machine: - -``` -$ ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no `terraform output username`@`terraform output public_ips` -# N.B.: the default username will differ depending on the AMI/OS you installed, e.g. ubuntu for Ubuntu, ec2-user for Red Hat, etc. -``` - -or - -``` -source ../setup.sh -tf_ssh 1 # Or the nth machine, if multiple VMs are provisioned. -``` - -## Resources - -* [https://www.terraform.io/docs/providers/aws/](https://www.terraform.io/docs/providers/aws/) -* [https://www.terraform.io/docs/providers/aws/r/instance.html](https://www.terraform.io/docs/providers/aws/r/instance.html) -* [Terraform variables](https://www.terraform.io/intro/getting-started/variables.html) diff --git a/tools/provisioning/aws/main.tf b/tools/provisioning/aws/main.tf deleted file mode 100755 index f4be8c34..00000000 --- a/tools/provisioning/aws/main.tf +++ /dev/null @@ -1,137 +0,0 @@ -# Specify the provider and access details -provider "aws" { - # Access key, secret key and region are sourced from environment variables or input arguments -- see README.md - region = "${var.aws_dc}" -} - -resource "aws_security_group" "allow_ssh" { - name = "${var.name}_allow_ssh" - description = "AWS security group to allow SSH-ing onto AWS EC2 instances (created using Terraform)." - - # Open TCP port for SSH: - ingress { - from_port = 22 - to_port = 22 - protocol = "tcp" - cidr_blocks = ["${var.client_ip}/32"] - } - - tags { - Name = "${var.name}_allow_ssh" - App = "${var.app}" - CreatedBy = "terraform" - } -} - -resource "aws_security_group" "allow_docker" { - name = "${var.name}_allow_docker" - description = "AWS security group to allow communication with Docker on AWS EC2 instances (created using Terraform)." - - # Open TCP port for Docker: - ingress { - from_port = 2375 - to_port = 2375 - protocol = "tcp" - cidr_blocks = ["${var.client_ip}/32"] - } - - tags { - Name = "${var.name}_allow_docker" - App = "${var.app}" - CreatedBy = "terraform" - } -} - -resource "aws_security_group" "allow_weave" { - name = "${var.name}_allow_weave" - description = "AWS security group to allow communication with Weave on AWS EC2 instances (created using Terraform)." - - # Open TCP port for Weave: - ingress { - from_port = 12375 - to_port = 12375 - protocol = "tcp" - cidr_blocks = ["${var.client_ip}/32"] - } - - tags { - Name = "${var.name}_allow_weave" - App = "${var.app}" - CreatedBy = "terraform" - } -} - -resource "aws_security_group" "allow_private_ingress" { - name = "${var.name}_allow_private_ingress" - description = "AWS security group to allow all private ingress traffic on AWS EC2 instances (created using Terraform)." - - # Full inbound local network access on both TCP and UDP - ingress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["${var.aws_vpc_cidr_block}"] - } - - tags { - Name = "${var.name}_allow_private_ingress" - App = "${var.app}" - CreatedBy = "terraform" - } -} - -resource "aws_security_group" "allow_all_egress" { - name = "${var.name}_allow_all_egress" - description = "AWS security group to allow all egress traffic on AWS EC2 instances (created using Terraform)." - - # Full outbound internet access on both TCP and UDP - egress { - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] - } - - tags { - Name = "${var.name}_allow_all_egress" - App = "${var.app}" - CreatedBy = "terraform" - } -} - -resource "aws_instance" "tf_test_vm" { - instance_type = "${var.aws_size}" - count = "${var.num_hosts}" - - # Lookup the correct AMI based on the region we specified - ami = "${lookup(var.aws_amis, var.aws_dc)}" - - key_name = "${var.aws_public_key_name}" - - security_groups = [ - "${aws_security_group.allow_ssh.name}", - "${aws_security_group.allow_docker.name}", - "${aws_security_group.allow_weave.name}", - "${aws_security_group.allow_private_ingress.name}", - "${aws_security_group.allow_all_egress.name}", - ] - - # Wait for machine to be SSH-able: - provisioner "remote-exec" { - inline = ["exit"] - - connection { - type = "ssh" - - # Lookup the correct username based on the AMI we specified - user = "${lookup(var.aws_usernames, "${lookup(var.aws_amis, var.aws_dc)}")}" - private_key = "${file("${var.aws_private_key_path}")}" - } - } - - tags { - Name = "${var.name}-${count.index}" - App = "${var.app}" - CreatedBy = "terraform" - } -} diff --git a/tools/provisioning/aws/outputs.tf b/tools/provisioning/aws/outputs.tf deleted file mode 100755 index 587986b8..00000000 --- a/tools/provisioning/aws/outputs.tf +++ /dev/null @@ -1,54 +0,0 @@ -output "username" { - value = "${lookup(var.aws_usernames, "${lookup(var.aws_amis, var.aws_dc)}")}" -} - -output "public_ips" { - value = ["${aws_instance.tf_test_vm.*.public_ip}"] -} - -output "hostnames" { - value = "${join("\n", - "${formatlist("%v.%v.%v", - aws_instance.tf_test_vm.*.tags.Name, - aws_instance.tf_test_vm.*.availability_zone, - var.app - )}" - )}" -} - -# /etc/hosts file for the Droplets: -output "private_etc_hosts" { - value = "${join("\n", - "${formatlist("%v %v.%v.%v", - aws_instance.tf_test_vm.*.private_ip, - aws_instance.tf_test_vm.*.tags.Name, - aws_instance.tf_test_vm.*.availability_zone, - var.app - )}" - )}" -} - -# /etc/hosts file for the client: -output "public_etc_hosts" { - value = "${join("\n", - "${formatlist("%v %v.%v.%v", - aws_instance.tf_test_vm.*.public_ip, - aws_instance.tf_test_vm.*.tags.Name, - aws_instance.tf_test_vm.*.availability_zone, - var.app - )}" - )}" -} - -output "ansible_inventory" { - value = "${format("[all]\n%s", join("\n", - "${formatlist("%v private_ip=%v", - aws_instance.tf_test_vm.*.public_ip, - aws_instance.tf_test_vm.*.private_ip, - )}" - ))}" -} - -output "private_key_path" { - value = "${var.aws_private_key_path}" -} diff --git a/tools/provisioning/aws/variables.tf b/tools/provisioning/aws/variables.tf deleted file mode 100755 index 5f4b4628..00000000 --- a/tools/provisioning/aws/variables.tf +++ /dev/null @@ -1,68 +0,0 @@ -variable "client_ip" { - description = "IP address of the client machine" -} - -variable "app" { - description = "Name of the application using the created EC2 instance(s)." - default = "default" -} - -variable "name" { - description = "Name of the EC2 instance(s)." - default = "test" -} - -variable "num_hosts" { - description = "Number of EC2 instance(s)." - default = 1 -} - -variable "aws_vpc_cidr_block" { - description = "AWS VPC CIDR block to use to attribute private IP addresses." - default = "172.31.0.0/16" -} - -variable "aws_public_key_name" { - description = "Name of the SSH keypair to use in AWS." -} - -variable "aws_private_key_path" { - description = "Path to file containing private key" - default = "~/.ssh/id_rsa" -} - -variable "aws_dc" { - description = "The AWS region to create things in." - default = "us-east-1" -} - -variable "aws_amis" { - default = { - # Ubuntu Server 16.04 LTS (HVM), SSD Volume Type: - "us-east-1" = "ami-40d28157" - "eu-west-2" = "ami-23d0da47" - - # Red Hat Enterprise Linux 7.3 (HVM), SSD Volume Type: - - #"us-east-1" = "ami-b63769a1" - - # CentOS 7 (x86_64) - with Updates HVM - - #"us-east-1" = "ami-6d1c2007" - } -} - -variable "aws_usernames" { - description = "User to SSH as into the AWS instance." - - default = { - "ami-40d28157" = "ubuntu" # Ubuntu Server 16.04 LTS (HVM) - "ami-b63769a1" = "ec2-user" # Red Hat Enterprise Linux 7.3 (HVM) - "ami-6d1c2007" = "centos" # CentOS 7 (x86_64) - with Updates HVM - } -} - -variable "aws_size" { - description = "AWS' selected machine size" - default = "t2.medium" # Instance with 2 cores & 4 GB memory -} diff --git a/tools/provisioning/do/README.md b/tools/provisioning/do/README.md deleted file mode 100755 index d958f18d..00000000 --- a/tools/provisioning/do/README.md +++ /dev/null @@ -1,98 +0,0 @@ -# Digital Ocean - -## Introduction - -This project allows you to get hold of some machine on Digital Ocean. -You can then use these machines as is or run various Ansible playbooks from `../config_management` to set up Weave Net, Kubernetes, etc. - -## Setup - -* Log in [cloud.digitalocean.com](https://cloud.digitalocean.com) with your account. - -* Go to `Settings` > `Security` > `SSH keys` > `Add SSH Key`. - Enter your SSH public key and the name for it, and click `Add SSH Key`. - Set the path to your private key as an environment variable: - -``` -export DIGITALOCEAN_SSH_KEY_NAME= -export TF_VAR_do_private_key_path="$HOME/.ssh/id_rsa" -``` - -* Go to `API` > `Tokens` > `Personal access tokens` > `Generate New Token` - Enter your token name and click `Generate Token` to get your 64-characters-long API token. - Set these as environment variables: - -``` -export DIGITALOCEAN_TOKEN_NAME="" -export DIGITALOCEAN_TOKEN= -``` - -* Run the following command to get the Digital Ocean ID for your SSH public key (e.g. `1234567`) and set it as an environment variable: - -``` -$ export TF_VAR_do_public_key_id=$(curl -s -X GET -H "Content-Type: application/json" \ --H "Authorization: Bearer $DIGITALOCEAN_TOKEN" "https://api.digitalocean.com/v2/account/keys" \ -| jq -c --arg key_name "$DIGITALOCEAN_SSH_KEY_NAME" '.ssh_keys | .[] | select(.name==$key_name) | .id') -``` - - or pass it as a Terraform variable: - -``` -$ terraform \ --var 'do_private_key_path=' \ --var 'do_public_key_id=' -``` - -### Bash aliases - -You can set the above variables temporarily in your current shell, permanently in your `~/.bashrc` file, or define aliases to activate/deactivate them at will with one single command by adding the below to your `~/.bashrc` file: - -``` -function _do_on() { - export DIGITALOCEAN_TOKEN_NAME="" # Replace with appropriate value. - export DIGITALOCEAN_TOKEN= # Replace with appropriate value. - export DIGITALOCEAN_SSH_KEY_NAME="" # Replace with appropriate value. - export TF_VAR_do_private_key_path="$HOME/.ssh/id_rsa" # Replace with appropriate value. - export TF_VAR_do_public_key_path="$HOME/.ssh/id_rsa.pub" # Replace with appropriate value. - export TF_VAR_do_public_key_id= # Replace with appropriate value. -} -alias _do_on='_do_on' -function _do_off() { - unset DIGITALOCEAN_TOKEN_NAME - unset DIGITALOCEAN_TOKEN - unset DIGITALOCEAN_SSH_KEY_NAME - unset TF_VAR_do_private_key_path - unset TF_VAR_do_public_key_path - unset TF_VAR_do_public_key_id -} -alias _do_off='_do_off' -``` - -N.B.: - -* sourcing `../setup.sh` defines aliases called `do_on` and `do_off`, similarly to the above (however, notice no `_` in front of the name, as opposed to the ones above); -* `../setup.sh`'s `do_on` alias needs the `SECRET_KEY` environment variable to be set in order to decrypt sensitive information. - -## Usage - -* Create the machine: `terraform apply` -* Show the machine's status: `terraform show` -* Stop and destroy the machine: `terraform destroy` -* SSH into the newly-created machine: - -``` -$ ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no `terraform output username`@`terraform output public_ips` -``` - -or - -``` -source ../setup.sh -tf_ssh 1 # Or the nth machine, if multiple VMs are provisioned. -``` - -## Resources - -* [https://www.terraform.io/docs/providers/do/](https://www.terraform.io/docs/providers/do/) -* [https://www.terraform.io/docs/providers/do/r/droplet.html](https://www.terraform.io/docs/providers/do/r/droplet.html) -* [Terraform variables](https://www.terraform.io/intro/getting-started/variables.html) diff --git a/tools/provisioning/do/main.tf b/tools/provisioning/do/main.tf deleted file mode 100755 index 834cdf7d..00000000 --- a/tools/provisioning/do/main.tf +++ /dev/null @@ -1,42 +0,0 @@ -provider "digitalocean" { - # See README.md for setup instructions. -} - -# Tags to label and organize droplets: -resource "digitalocean_tag" "name" { - name = "${var.name}" -} - -resource "digitalocean_tag" "app" { - name = "${var.app}" -} - -resource "digitalocean_tag" "terraform" { - name = "terraform" -} - -resource "digitalocean_droplet" "tf_test_vm" { - ssh_keys = ["${var.do_public_key_id}"] - image = "${var.do_os}" - region = "${var.do_dc}" - size = "${var.do_size}" - name = "${var.name}-${count.index}" - count = "${var.num_hosts}" - - tags = [ - "${var.app}", - "${var.name}", - "terraform", - ] - - # Wait for machine to be SSH-able: - provisioner "remote-exec" { - inline = ["exit"] - - connection { - type = "ssh" - user = "${var.do_username}" - private_key = "${file("${var.do_private_key_path}")}" - } - } -} diff --git a/tools/provisioning/do/outputs.tf b/tools/provisioning/do/outputs.tf deleted file mode 100755 index 5f0ff455..00000000 --- a/tools/provisioning/do/outputs.tf +++ /dev/null @@ -1,57 +0,0 @@ -output "username" { - value = "${var.do_username}" -} - -output "public_ips" { - value = ["${digitalocean_droplet.tf_test_vm.*.ipv4_address}"] -} - -output "hostnames" { - value = "${join("\n", - "${formatlist("%v.%v.%v", - digitalocean_droplet.tf_test_vm.*.name, - digitalocean_droplet.tf_test_vm.*.region, - var.app - )}" - )}" -} - -# /etc/hosts file for the Droplets: -# N.B.: by default Digital Ocean droplets only have public IPs, but in order to -# be consistent with other providers' recipes, we provide an output to generate -# an /etc/hosts file on the Droplets, even though it is using public IPs only. -output "private_etc_hosts" { - value = "${join("\n", - "${formatlist("%v %v.%v.%v", - digitalocean_droplet.tf_test_vm.*.ipv4_address, - digitalocean_droplet.tf_test_vm.*.name, - digitalocean_droplet.tf_test_vm.*.region, - var.app - )}" - )}" -} - -# /etc/hosts file for the client: -output "public_etc_hosts" { - value = "${join("\n", - "${formatlist("%v %v.%v.%v", - digitalocean_droplet.tf_test_vm.*.ipv4_address, - digitalocean_droplet.tf_test_vm.*.name, - digitalocean_droplet.tf_test_vm.*.region, - var.app - )}" - )}" -} - -output "ansible_inventory" { - value = "${format("[all]\n%s", join("\n", - "${formatlist("%v private_ip=%v", - digitalocean_droplet.tf_test_vm.*.ipv4_address, - digitalocean_droplet.tf_test_vm.*.ipv4_address - )}" - ))}" -} - -output "private_key_path" { - value = "${var.do_private_key_path}" -} diff --git a/tools/provisioning/do/variables.tf b/tools/provisioning/do/variables.tf deleted file mode 100755 index 6f7f40ed..00000000 --- a/tools/provisioning/do/variables.tf +++ /dev/null @@ -1,185 +0,0 @@ -variable "client_ip" { - description = "IP address of the client machine" -} - -variable "app" { - description = "Name of the application using the created droplet(s)." - default = "default" -} - -variable "name" { - description = "Name of the droplet(s)." - default = "test" -} - -variable "num_hosts" { - description = "Number of droplet(s)." - default = 1 -} - -variable "do_private_key_path" { - description = "Digital Ocean SSH private key path" - default = "~/.ssh/id_rsa" -} - -variable "do_public_key_id" { - description = "Digital Ocean ID for your SSH public key" - - # You can retrieve it and set it as an environment variable this way: - - # $ export TF_VAR_do_public_key_id=$(curl -s -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" "https://api.digitalocean.com/v2/account/keys" | jq -c --arg key_name "$DIGITALOCEAN_SSH_KEY_NAME" '.ssh_keys | .[] | select(.name==$key_name) | .id') -} - -variable "do_username" { - description = "Digital Ocean SSH username" - default = "root" -} - -variable "do_os" { - description = "Digital Ocean OS" - default = "ubuntu-16-04-x64" -} - -# curl -s -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" "https://api.digitalocean.com/v2/images?page=1&per_page=999999" | jq ".images | .[] | .slug" | grep -P "ubuntu|coreos|centos" | grep -v alpha | grep -v beta -# "ubuntu-16-04-x32" -# "ubuntu-16-04-x64" -# "ubuntu-16-10-x32" -# "ubuntu-16-10-x64" -# "ubuntu-14-04-x32" -# "ubuntu-14-04-x64" -# "ubuntu-12-04-x64" -# "ubuntu-12-04-x32" -# "coreos-stable" -# "centos-6-5-x32" -# "centos-6-5-x64" -# "centos-7-0-x64" -# "centos-7-x64" -# "centos-6-x64" -# "centos-6-x32" -# "centos-5-x64" -# "centos-5-x32" - -# Digital Ocean datacenters -# See also: -# $ curl -s -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" "https://api.digitalocean.com/v2/regions" | jq -c ".regions | .[] | .slug" | sort -u - -variable "do_dc_ams2" { - description = "Digital Ocean Amsterdam Datacenter 2" - default = "ams2" -} - -variable "do_dc_ams3" { - description = "Digital Ocean Amsterdam Datacenter 3" - default = "ams3" -} - -variable "do_dc_blr1" { - description = "Digital Ocean Bangalore Datacenter 1" - default = "blr1" -} - -variable "do_dc_fra1" { - description = "Digital Ocean Frankfurt Datacenter 1" - default = "fra1" -} - -variable "do_dc_lon1" { - description = "Digital Ocean London Datacenter 1" - default = "lon1" -} - -variable "do_dc_nyc1" { - description = "Digital Ocean New York Datacenter 1" - default = "nyc1" -} - -variable "do_dc_nyc2" { - description = "Digital Ocean New York Datacenter 2" - default = "nyc2" -} - -variable "do_dc_nyc3" { - description = "Digital Ocean New York Datacenter 3" - default = "nyc3" -} - -variable "do_dc_sfo1" { - description = "Digital Ocean San Francisco Datacenter 1" - default = "sfo1" -} - -variable "do_dc_sfo2" { - description = "Digital Ocean San Francisco Datacenter 2" - default = "sfo2" -} - -variable "do_dc_sgp1" { - description = "Digital Ocean Singapore Datacenter 1" - default = "sgp1" -} - -variable "do_dc_tor1" { - description = "Digital Ocean Toronto Datacenter 1" - default = "tor1" -} - -variable "do_dc" { - description = "Digital Ocean's selected datacenter" - default = "lon1" -} - -variable "do_size" { - description = "Digital Ocean's selected machine size" - default = "4gb" -} - -# Digital Ocean sizes - - -# See also: - - -# $ curl -s -X GET -H "Content-Type: application/json" -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" "https://api.digitalocean.com/v2/sizes" | jq -c ".sizes | .[] | .slug" - - -# "512mb" - - -# "1gb" - - -# "2gb" - - -# "4gb" - - -# "8gb" - - -# "16gb" - - -# "m-16gb" - - -# "32gb" - - -# "m-32gb" - - -# "48gb" - - -# "m-64gb" - - -# "64gb" - - -# "m-128gb" - - -# "m-224gb" - diff --git a/tools/provisioning/gcp/README.md b/tools/provisioning/gcp/README.md deleted file mode 100755 index b2d6622c..00000000 --- a/tools/provisioning/gcp/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# Google Cloud Platform - -## Introduction - -This project allows you to get hold of some machine on Google Cloud Platform. -You can then use these machines as is or run various Ansible playbooks from `../config_management` to set up Weave Net, Kubernetes, etc. - -## Setup - -* Log in [console.cloud.google.com](https://console.cloud.google.com) with your Google account. - -* Go to `API Manager` > `Credentials` > `Create credentials` > `Service account key`, - in `Service account`, select `Compute Engine default service account`, - in `Key type`, select `JSON`, and then click `Create`. - -* This will download a JSON file to your machine. Place this file wherever you want and then create the following environment variables: - -``` -$ export GOOGLE_CREDENTIALS_FILE="path/to/your.json" -$ export GOOGLE_CREDENTIALS=$(cat "$GOOGLE_CREDENTIALS_FILE") -``` - -* Go to `Compute Engine` > `Metadata` > `SSH keys` and add your username and SSH public key; - or - set it up using `gcloud compute project-info add-metadata --metadata-from-file sshKeys=~/.ssh/id_rsa.pub`. - If you used your default SSH key (i.e. `~/.ssh/id_rsa.pub`), then you do not have anything to do. - Otherwise, you will have to either define the below environment variable: - -``` -$ export TF_VAR_gcp_public_key_path= -$ export TF_VAR_gcp_private_key_path= -``` - - or to pass these as Terraform variables: - -``` -$ terraform \ --var 'gcp_public_key_path=' \ --var 'gcp_private_key_path=' -``` - -* Set the username in your public key as an environment variable. - This will be used as the username of the Linux account created on the machine, which you will need to SSH into it later on. - - N.B.: - * GCP already has the username set from the SSH public key you uploaded in the previous step. - * If your username is an email address, e.g. `name@domain.com`, then GCP uses `name` as the username. - -``` -export TF_VAR_gcp_username= -``` - -* Set your current IP address as an environment variable: - -``` -export TF_VAR_client_ip=$(curl -s -X GET http://checkip.amazonaws.com/) -``` - - or pass it as a Terraform variable: - -``` -$ terraform -var 'client_ip=$(curl -s -X GET http://checkip.amazonaws.com/)' -``` - -* Set your project as an environment variable: - -``` -export TF_VAR_gcp_project=weave-net-tests -``` - - or pass it as a Terraform variable: - -``` -$ terraform -var 'gcp_project=weave-net-tests' -``` - -### Bash aliases - -You can set the above variables temporarily in your current shell, permanently in your `~/.bashrc` file, or define aliases to activate/deactivate them at will with one single command by adding the below to your `~/.bashrc` file: - -``` -function _gcp_on() { - export GOOGLE_CREDENTIALS_FILE="&authuser=1 - region = "${var.gcp_region}" - - project = "${var.gcp_project}" -} - -resource "google_compute_instance" "tf_test_vm" { - name = "${var.name}-${count.index}" - machine_type = "${var.gcp_size}" - zone = "${var.gcp_zone}" - count = "${var.num_hosts}" - - disk { - image = "${var.gcp_image}" - } - - tags = [ - "${var.app}", - "${var.name}", - "terraform", - ] - - network_interface { - network = "${var.gcp_network}" - - access_config { - // Ephemeral IP - } - } - - metadata { - ssh-keys = "${var.gcp_username}:${file("${var.gcp_public_key_path}")}" - } - - # Wait for machine to be SSH-able: - provisioner "remote-exec" { - inline = ["exit"] - - connection { - type = "ssh" - user = "${var.gcp_username}" - private_key = "${file("${var.gcp_private_key_path}")}" - } - } -} - -resource "google_compute_firewall" "fw-allow-docker-and-weave" { - name = "${var.name}-allow-docker-and-weave" - network = "${var.gcp_network}" - target_tags = ["${var.name}"] - - allow { - protocol = "tcp" - ports = ["2375", "12375"] - } - - source_ranges = ["${var.client_ip}"] -} - -# Required for FastDP crypto in Weave Net: -resource "google_compute_firewall" "fw-allow-esp" { - name = "${var.name}-allow-esp" - network = "${var.gcp_network}" - target_tags = ["${var.name}"] - - allow { - protocol = "esp" - } - - source_ranges = ["${var.gcp_network_global_cidr}"] -} diff --git a/tools/provisioning/gcp/outputs.tf b/tools/provisioning/gcp/outputs.tf deleted file mode 100755 index 9aa1e33e..00000000 --- a/tools/provisioning/gcp/outputs.tf +++ /dev/null @@ -1,66 +0,0 @@ -output "username" { - value = "${var.gcp_username}" -} - -output "public_ips" { - value = ["${google_compute_instance.tf_test_vm.*.network_interface.0.access_config.0.assigned_nat_ip}"] -} - -output "hostnames" { - value = "${join("\n", - "${formatlist("%v.%v.%v", - google_compute_instance.tf_test_vm.*.name, - google_compute_instance.tf_test_vm.*.zone, - var.app - )}" - )}" -} - -# /etc/hosts file for the Compute Engine instances: -output "private_etc_hosts" { - value = "${join("\n", - "${formatlist("%v %v.%v.%v", - google_compute_instance.tf_test_vm.*.network_interface.0.address, - google_compute_instance.tf_test_vm.*.name, - google_compute_instance.tf_test_vm.*.zone, - var.app - )}" - )}" -} - -# /etc/hosts file for the client: -output "public_etc_hosts" { - value = "${join("\n", - "${formatlist("%v %v.%v.%v", - google_compute_instance.tf_test_vm.*.network_interface.0.access_config.0.assigned_nat_ip, - google_compute_instance.tf_test_vm.*.name, - google_compute_instance.tf_test_vm.*.zone, - var.app - )}" - )}" -} - -output "ansible_inventory" { - value = "${format("[all]\n%s", join("\n", - "${formatlist("%v private_ip=%v", - google_compute_instance.tf_test_vm.*.network_interface.0.access_config.0.assigned_nat_ip, - google_compute_instance.tf_test_vm.*.network_interface.0.address - )}" - ))}" -} - -output "private_key_path" { - value = "${var.gcp_private_key_path}" -} - -output "instances_names" { - value = ["${google_compute_instance.tf_test_vm.*.name}"] -} - -output "image" { - value = "${var.gcp_image}" -} - -output "zone" { - value = "${var.gcp_zone}" -} diff --git a/tools/provisioning/gcp/variables.tf b/tools/provisioning/gcp/variables.tf deleted file mode 100755 index 6b2027b2..00000000 --- a/tools/provisioning/gcp/variables.tf +++ /dev/null @@ -1,77 +0,0 @@ -variable "gcp_username" { - description = "Google Cloud Platform SSH username" -} - -variable "app" { - description = "Name of the application using the created Compute Engine instance(s)." - default = "default" -} - -variable "name" { - description = "Name of the Compute Engine instance(s)." - default = "test" -} - -variable "num_hosts" { - description = "Number of Compute Engine instance(s)." - default = 1 -} - -variable "client_ip" { - description = "IP address of the client machine" -} - -variable "gcp_public_key_path" { - description = "Path to file containing public key" - default = "~/.ssh/id_rsa.pub" -} - -variable "gcp_private_key_path" { - description = "Path to file containing private key" - default = "~/.ssh/id_rsa" -} - -variable "gcp_project" { - description = "Google Cloud Platform project" - default = "weave-net-tests" -} - -variable "gcp_image" { - # See also: https://cloud.google.com/compute/docs/images - # For example: - # - "ubuntu-os-cloud/ubuntu-1604-lts" - # - "debian-cloud/debian-8" - # - "centos-cloud/centos-7" - # - "rhel-cloud/rhel7" - description = "Google Cloud Platform OS" - - default = "ubuntu-os-cloud/ubuntu-1604-lts" -} - -variable "gcp_size" { - # See also: - # $ gcloud compute machine-types list - description = "Google Cloud Platform's selected machine size" - - default = "n1-standard-1" -} - -variable "gcp_region" { - description = "Google Cloud Platform's selected region" - default = "us-central1" -} - -variable "gcp_zone" { - description = "Google Cloud Platform's selected zone" - default = "us-central1-a" -} - -variable "gcp_network" { - description = "Google Cloud Platform's selected network" - default = "test" -} - -variable "gcp_network_global_cidr" { - description = "CIDR covering all regions for the selected Google Cloud Platform network" - default = "10.128.0.0/9" -} diff --git a/tools/provisioning/setup.sh b/tools/provisioning/setup.sh deleted file mode 100755 index 456878e0..00000000 --- a/tools/provisioning/setup.sh +++ /dev/null @@ -1,361 +0,0 @@ -#!/bin/bash -# -# Description: -# Helper functions to programmatically provision (e.g. for CIT). -# Aliases on these functions are also created so that this script can be -# sourced in your shell, in your ~/.bashrc file, etc. and directly called. -# -# Usage: -# Source this file and call the relevant functions. -# - -function ssh_public_key() { - echo -e "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDZBgLQts30PYXEMJnCU21QC+1ZE0Sv/Ry48Au3nYXn1KNoW/7C2qQ3KO2ZnpZRHCstFiU8QIlB9edi0cgcAoDWBkCiFBZEORxMvohWtrRQzf+x59o48lVjA/Fn7G+9hmavhLaDf6Qe7OhH8XUshNtnIQIUvNEWXKE75k32wUbuF8ibhJNpOOYKL4tVXK6IIKg6jR88BwGKPY/NZCl/HbhjnDJY0zCU1pZSprN6o/S953y/XXVozkh1772fCNeu4USfbt0oZOEJ57j6EWwEYIJhoeAEMAoD8ELt/bc/5iex8cuarM4Uib2JHO6WPWbBQ0NlrARIOKLrxkjjfGWarOLWBAgvwQn5zLg1pKb7aI4+jbA+ZSrII5B2HuYE9MDlU8NPL4pHrRfapGLkG/Fe9zNPvScXh+9iSWfD6G5ZoISutjiJO/iVYN0QSuj9QEIj9tl20czFz3Dhnq4sPPl5hoLunyQfajY7C/ipv6ilJyrEc0V6Z9FdPhpEI+HOgJr2vDQTFscQuyfWuzGJDZf6zPdZWo2pBql9E7piARuNAjakylGar/ebkCgfy28XQoDbDT0P0VYp+E8W5EYacx+zc5MuNhRTvbsO12fydT8V61MtA78wM/b0059feph+0zTykEHk670mYVoE3erZX+U1/BVBLSV9QzopO6/Pgx2ryriJfQ== weaveworks-cit" -} - -function decrypt() { - if [ -z "$1" ]; then - echo >&2 "Failed to decode and decrypt $2: no secret key was provided." - return 1 - fi - echo "$3" | openssl base64 -d | openssl enc -d -aes256 -pass "pass:$1" -} - -function ssh_private_key() { - # The private key has been AES256-encrypted and then Base64-encoded using the following command: - # $ openssl enc -in /tmp/weaveworks_cit_id_rsa -e -aes256 -pass stdin | openssl base64 > /tmp/weaveworks_cit_id_rsa.aes.b64 - # The below command does the reverse, i.e. base64-decode and AES-decrypt the file, and prints it to stdout. - # N.B.: Ask the password to Marc, or otherwise re-generate the SSH key using: - # $ ssh-keygen -t rsa -b 4096 -C "weaveworks-cit" - decrypt "$1" "SSH private key" "$( - cat <&2 "Failed to decode and decrypt SSH private key: no secret key was provided." - return 1 - fi - local ssh_private_key_path="$HOME/.ssh/weaveworks_cit_id_rsa" - [ -e "$ssh_private_key_path" ] && rm -f "$ssh_private_key_path" - ssh_private_key "$1" >"$ssh_private_key_path" - chmod 400 "$ssh_private_key_path" - echo "$ssh_private_key_path" -} - -function gcp_credentials() { - # The below GCP service account JSON credentials have been AES256-encrypted and then Base64-encoded using the following command: - # $ openssl enc -in ~/.ssh/weaveworks-cit.json -e -aes256 -pass stdin | openssl base64 > /tmp/weaveworks-cit.json.aes.b64 - # The below command does the reverse, i.e. base64-decode and AES-decrypt the file, and prints it to stdout. - # N.B.: Ask the password to Marc, or otherwise re-generate the credentials for GCP, as per ../tools/provisioning/gcp/README.md. - decrypt "$1" "JSON credentials" "$( - cat <&2 "Failed to configure for Digital Ocean: no value for the SECRET_KEY environment variable." - return 1 - fi - - # SSH public key: - export TF_VAR_do_public_key_path="$HOME/.ssh/weaveworks_cit_id_rsa.pub" - ssh_public_key >"$TF_VAR_do_public_key_path" - export DIGITALOCEAN_SSH_KEY_NAME="weaveworks-cit" - export TF_VAR_do_public_key_id=5228799 - - # SSH private key: - export TF_VAR_do_private_key_path=$(set_up_ssh_private_key "$SECRET_KEY") - - # API token: - # The below Digital Ocean token has been AES256-encrypted and then Base64-encoded using the following command: - # $ openssl enc -in /tmp/digital_ocean_token.txt -e -aes256 -pass stdin | openssl base64 > /tmp/digital_ocean_token.txt.aes.b64 - # The below command does the reverse, i.e. base64-decode and AES-decrypt the file, and prints it to stdout. - # N.B.: Ask the password to Marc, or otherwise re-generate the token for Digital Ocean, as per ../tools/provisioning/do/README.md. - export DIGITALOCEAN_TOKEN=$(decrypt "$SECRET_KEY" "Digital Ocean token" "U2FsdGVkX1/Gq5Rj9dDDraME8xK30JOyJ9dhfQzPBaaePJHqDPIG6of71DdJW0UyFUyRtbRflCPaZ8Um1pDJpU5LoNWQk4uCApC8+xciltT73uQtttLBG8FqgFBvYIHS") - export DIGITALOCEAN_TOKEN_NAME="weaveworks-cit" - export TF_VAR_client_ip=$(curl -s -X GET http://checkip.amazonaws.com/) -} -alias do_on='do_on' - -function do_off() { - unset TF_VAR_do_public_key_path - unset DIGITALOCEAN_SSH_KEY_NAME - unset TF_VAR_do_public_key_id - unset TF_VAR_do_private_key_path - unset DIGITALOCEAN_TOKEN - unset DIGITALOCEAN_TOKEN_NAME - unset TF_VAR_client_ip -} -alias do_off='do_off' - -# shellcheck disable=2155 -function gcp_on() { - # Set up everything required to run tests on GCP. - # Steps from ../tools/provisioning/gcp/README.md have been followed. - # All sensitive files have been encrypted, see respective functions. - if [ -z "$SECRET_KEY" ]; then - echo >&2 "Failed to configure for Google Cloud Platform: no value for the SECRET_KEY environment variable." - return 1 - fi - - # SSH public key and SSH username: - export TF_VAR_gcp_public_key_path="$HOME/.ssh/weaveworks_cit_id_rsa.pub" - ssh_public_key >"$TF_VAR_gcp_public_key_path" - export TF_VAR_gcp_username=$(cut -d' ' -f3 "$TF_VAR_gcp_public_key_path" | cut -d'@' -f1) - - # SSH private key: - export TF_VAR_gcp_private_key_path=$(set_up_ssh_private_key "$SECRET_KEY") - - # JSON credentials: - export GOOGLE_CREDENTIALS_FILE="$HOME/.ssh/weaveworks-cit.json" - [ -e "$GOOGLE_CREDENTIALS_FILE" ] && rm -f "$GOOGLE_CREDENTIALS_FILE" - gcp_credentials "$SECRET_KEY" >"$GOOGLE_CREDENTIALS_FILE" - chmod 400 "$GOOGLE_CREDENTIALS_FILE" - export GOOGLE_CREDENTIALS=$(cat "$GOOGLE_CREDENTIALS_FILE") - - export TF_VAR_client_ip=$(curl -s -X GET http://checkip.amazonaws.com/) - export TF_VAR_gcp_project="${PROJECT:-"weave-net-tests"}" - # shellcheck disable=2015 - [ -z "$PROJECT" ] && echo >&2 "WARNING: no value provided for PROJECT environment variable: defaulted it to $TF_VAR_gcp_project." || true -} -alias gcp_on='gcp_on' - -function gcp_off() { - unset TF_VAR_gcp_public_key_path - unset TF_VAR_gcp_username - unset TF_VAR_gcp_private_key_path - unset GOOGLE_CREDENTIALS_FILE - unset GOOGLE_CREDENTIALS - unset TF_VAR_client_ip - unset TF_VAR_gcp_project -} -alias gcp_off='gcp_off' - -# shellcheck disable=2155 -function aws_on() { - # Set up everything required to run tests on Amazon Web Services. - # Steps from ../tools/provisioning/aws/README.md have been followed. - # All sensitive files have been encrypted, see respective functions. - if [ -z "$SECRET_KEY" ]; then - echo >&2 "Failed to configure for Amazon Web Services: no value for the SECRET_KEY environment variable." - return 1 - fi - - # SSH public key: - export TF_VAR_aws_public_key_name="weaveworks_cit_id_rsa" - - # SSH private key: - export TF_VAR_aws_private_key_path=$(set_up_ssh_private_key "$SECRET_KEY") - - # The below AWS access key ID and secret access key have been AES256-encrypted and then Base64-encoded using the following commands: - # $ openssl enc -in /tmp/aws_access_key_id.txt -e -aes256 -pass stdin | openssl base64 > /tmp/aws_access_key_id.txt.aes.b64 - # $ openssl enc -in /tmp/aws_secret_access_key.txt -e -aes256 -pass stdin | openssl base64 > /tmp/aws_secret_access_key.txt.aes.b64 - # The below commands do the reverse, i.e. base64-decode and AES-decrypt the encrypted and encoded strings, and print it to stdout. - # N.B.: Ask the password to Marc, or otherwise re-generate the AWS access key ID and secret access key, as per ../tools/provisioning/aws/README.md. - export AWS_ACCESS_KEY_ID="$(decrypt "$SECRET_KEY" "AWS access key ID" "U2FsdGVkX18Txjm2PWSlJsToYm1vv4dMTtVLkRNiQbrC6Y6GuIHb1ao5MmGPJ1wf")" - export AWS_SECRET_ACCESS_KEY="$(decrypt "$SECRET_KEY" "AWS secret access key" "$( - cat <&2 <<-EOF -ERROR: $1 - -Usage: - \$ tf_ssh [OPTION]... -Examples: - \$ tf_ssh 1 - \$ tf_ssh 1 -o LogLevel VERBOSE - \$ tf_ssh 1 -i ~/.ssh/custom_private_key_id_rsa -Available machines: -EOF - cat -n >&2 <<<"$(terraform output public_etc_hosts)" -} - -# shellcheck disable=SC2155 -function tf_ssh() { - [ -z "$1" ] && tf_ssh_usage "No host ID provided." && return 1 - local ip="$(sed "$1q;d" <<<"$(terraform output public_etc_hosts)" | cut -d ' ' -f 1)" - shift # Drop the first argument, corresponding to the machine ID, to allow passing other arguments to SSH using "$@" -- see below. - [ -z "$ip" ] && tf_ssh_usage "Invalid host ID provided." && return 1 - # shellcheck disable=SC2029 - ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "$@" "$(terraform output username)@$ip" -} -alias tf_ssh='tf_ssh' - -function tf_ansi_usage() { - cat >&2 <<-EOF -ERROR: $1 - -Usage: - \$ tf_ansi [OPTION]... -Examples: - \$ tf_ansi setup_weave-net_dev - \$ tf_ansi 1 - \$ tf_ansi 1 -vvv --private-key=~/.ssh/custom_private_key_id_rsa - \$ tf_ansi setup_weave-kube --extra-vars "docker_version=1.12.6 kubernetes_version=1.5.6" -Available playbooks: -EOF - cat -n >&2 <<<"$(for file in "$(dirname "${BASH_SOURCE[0]}")"/../../config_management/*.yml; do basename "$file" | sed 's/.yml//'; done)" -} - -# shellcheck disable=SC2155,SC2064 -function tf_ansi() { - [ -z "$1" ] && tf_ansi_usage "No Ansible playbook provided." && return 1 - local id="$1" - shift # Drop the first argument to allow passing other arguments to Ansible using "$@" -- see below. - if [[ "$id" =~ ^[0-9]+$ ]]; then - local playbooks=(../../config_management/*.yml) - local path="${playbooks[(($id - 1))]}" # Select the ith entry in the list of playbooks (0-based). - else - local path="$(dirname "${BASH_SOURCE[0]}")/../../config_management/$id.yml" - fi - local inventory="$(mktemp /tmp/ansible_inventory_XXX)" - trap 'rm -f $inventory' SIGINT SIGTERM RETURN - echo -e "$(terraform output ansible_inventory)" >"$inventory" - [ ! -r "$path" ] && tf_ansi_usage "Ansible playbook not found: $path" && return 1 - ansible-playbook "$@" -u "$(terraform output username)" -i "$inventory" --ssh-extra-args="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" "$path" -} -alias tf_ansi='tf_ansi' diff --git a/tools/publish-site b/tools/publish-site deleted file mode 100755 index 10760404..00000000 --- a/tools/publish-site +++ /dev/null @@ -1,50 +0,0 @@ -#!/bin/bash - -set -e -set -o pipefail - -: "${PRODUCT:=}" - -fatal() { - echo "$@" >&2 - exit 1 -} - -if [ ! -d .git ]; then - fatal "Current directory is not a git clone" -fi - -if [ -z "${PRODUCT}" ]; then - fatal "Must specify PRODUCT" -fi - -if ! BRANCH=$(git symbolic-ref --short HEAD) || [ -z "$BRANCH" ]; then - fatal "Could not determine branch" -fi - -case "$BRANCH" in - issues/*) - VERSION="${BRANCH#issues/}" - TAGS="$VERSION" - ;; - *) - if echo "$BRANCH" | grep -qE '^[0-9]+\.[0-9]+'; then - DESCRIBE=$(git describe --match 'v*') - if ! VERSION=$(echo "$DESCRIBE" | grep -oP '(?<=^v)[0-9]+\.[0-9]+\.[0-9]+'); then - fatal "Could not infer latest $BRANCH version from $DESCRIBE" - fi - TAGS="$VERSION latest" - else - VERSION="$BRANCH" - TAGS="$VERSION" - fi - ;; -esac - -for TAG in $TAGS; do - echo ">>> Publishing $PRODUCT $VERSION to $1/docs/$PRODUCT/$TAG" - wordepress \ - --url "$1" --user "$2" --password "$3" \ - --product "$PRODUCT" --version "$VERSION" --tag "$TAG" \ - publish site -done diff --git a/tools/push-images b/tools/push-images deleted file mode 100755 index 60edac7c..00000000 --- a/tools/push-images +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -QUAY_PREFIX=quay.io/ -IMAGES=$(make images) -IMAGE_TAG=$(./tools/image-tag) - -usage() { - echo "$0 [-no-docker-hub]" -} - -NO_DOCKER_HUB= -while [ $# -gt 0 ]; do - case "$1" in - -no-docker-hub) - NO_DOCKER_HUB=1 - shift 1 - ;; - *) - usage - exit 2 - ;; - esac -done - -pids="" -for image in ${IMAGES}; do - if [[ "$image" == *"build"* ]]; then - continue - fi - echo "Will push ${image}:${IMAGE_TAG}" - docker push "${image}:${IMAGE_TAG}" & - pids="$pids $!" - - if [ -z "$NO_DOCKER_HUB" ]; then - # remove the quey prefix and push to docker hub - docker_hub_image=${image#$QUAY_PREFIX} - docker tag "${image}:${IMAGE_TAG}" "${docker_hub_image}:${IMAGE_TAG}" - echo "Will push ${docker_hub_image}:${IMAGE_TAG}" - docker push "${docker_hub_image}:${IMAGE_TAG}" & - pids="$pids $!" - fi -done - -# Wait individually for tasks so we fail-exit on any non-zero return code -for p in $pids; do - wait $p -done - -wait diff --git a/tools/rebuild-image b/tools/rebuild-image deleted file mode 100755 index 1f0bb109..00000000 --- a/tools/rebuild-image +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash -# Rebuild a cached docker image if the input files have changed. -# Usage: ./rebuild-image - -set -eux - -IMAGENAME=$1 -# shellcheck disable=SC2001 -SAVEDNAME=$(echo "$IMAGENAME" | sed "s/[\/\-]/\./g") -IMAGEDIR=$2 -shift 2 - -INPUTFILES=("$@") -CACHEDIR=$HOME/docker/ - -# Rebuild the image -rebuild() { - mkdir -p "$CACHEDIR" - rm "$CACHEDIR/$SAVEDNAME"* || true - docker build -t "$IMAGENAME" "$IMAGEDIR" - docker save "$IMAGENAME:latest" | gzip - >"$CACHEDIR/$SAVEDNAME-$CIRCLE_SHA1.gz" -} - -# Get the revision the cached image was build at -cached_image_rev() { - find "$CACHEDIR" -name "$SAVEDNAME-*" -type f | sed -n 's/^[^\-]*\-\([a-z0-9]*\).gz$/\1/p' -} - -# Have there been any revision between $1 and $2 -has_changes() { - local rev1=$1 - local rev2=$2 - local changes - changes=$(git diff --oneline "$rev1..$rev2" -- "${INPUTFILES[@]}" | wc -l) - [ "$changes" -gt 0 ] -} - -commit_timestamp() { - local rev=$1 - git show -s --format=%ct "$rev" -} - -# Is the SHA1 actually present in the repo? -# It could be it isn't, e.g. after a force push -is_valid_commit() { - local rev=$1 - git rev-parse --quiet --verify "$rev^{commit}" >/dev/null -} - -cached_revision=$(cached_image_rev) -if [ -z "$cached_revision" ]; then - echo ">>> No cached image found; rebuilding" - rebuild - exit 0 -fi - -if ! is_valid_commit "$cached_revision"; then - echo ">>> Git commit of cached image not found in repo; rebuilding" - rebuild - exit 0 -fi - -echo ">>> Found cached image rev $cached_revision" -if has_changes "$cached_revision" "$CIRCLE_SHA1"; then - echo ">>> Found changes, rebuilding" - rebuild - exit 0 -fi - -IMAGE_TIMEOUT="$((3 * 24 * 60 * 60))" -if [ "$(commit_timestamp "$cached_revision")" -lt "${IMAGE_TIMEOUT}" ]; then - echo ">>> Image is more the 24hrs old; rebuilding" - rebuild - exit 0 -fi - -# we didn't rebuild; import cached version -echo ">>> No changes found, importing cached image" -zcat "$CACHEDIR/$SAVEDNAME-$cached_revision.gz" | docker load diff --git a/tools/runner/Makefile b/tools/runner/Makefile deleted file mode 100644 index f19bcc7d..00000000 --- a/tools/runner/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -.PHONY: all clean - -all: runner - -runner: *.go - go get -tags netgo ./$(@D) - go build -ldflags "-extldflags \"-static\" -linkmode=external" -tags netgo -o $@ ./$(@D) - -clean: - rm -rf runner - go clean ./... diff --git a/tools/runner/runner.go b/tools/runner/runner.go deleted file mode 100644 index 38e5a62c..00000000 --- a/tools/runner/runner.go +++ /dev/null @@ -1,290 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "net/http" - "net/url" - "os" - "os/exec" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/mgutz/ansi" - "github.com/weaveworks/common/mflag" -) - -const ( - defaultSchedulerHost = "positive-cocoa-90213.appspot.com" - jsonContentType = "application/json" -) - -var ( - start = ansi.ColorCode("black+ub") - fail = ansi.ColorCode("red+b") - succ = ansi.ColorCode("green+b") - reset = ansi.ColorCode("reset") - - schedulerHost = defaultSchedulerHost - useScheduler = false - runParallel = false - verbose = false - timeout = 180 // In seconds. Three minutes ought to be enough for any test - - consoleLock = sync.Mutex{} -) - -type test struct { - name string - hosts int -} - -type schedule struct { - Tests []string `json:"tests"` -} - -type result struct { - test - errored bool - hosts []string -} - -type tests []test - -func (ts tests) Len() int { return len(ts) } -func (ts tests) Swap(i, j int) { ts[i], ts[j] = ts[j], ts[i] } -func (ts tests) Less(i, j int) bool { - if ts[i].hosts != ts[j].hosts { - return ts[i].hosts < ts[j].hosts - } - return ts[i].name < ts[j].name -} - -func (ts *tests) pick(available int) (test, bool) { - // pick the first test that fits in the available hosts - for i, test := range *ts { - if test.hosts <= available { - *ts = append((*ts)[:i], (*ts)[i+1:]...) - return test, true - } - } - - return test{}, false -} - -func (t test) run(hosts []string) bool { - consoleLock.Lock() - fmt.Printf("%s>>> Running %s on %s%s\n", start, t.name, hosts, reset) - consoleLock.Unlock() - - var out bytes.Buffer - - cmd := exec.Command(t.name) - cmd.Env = os.Environ() - cmd.Stdout = &out - cmd.Stderr = &out - - // replace HOSTS in env - for i, env := range cmd.Env { - if strings.HasPrefix(env, "HOSTS") { - cmd.Env[i] = fmt.Sprintf("HOSTS=%s", strings.Join(hosts, " ")) - break - } - } - - start := time.Now() - var err error - - c := make(chan error, 1) - go func() { c <- cmd.Run() }() - select { - case err = <-c: - case <-time.After(time.Duration(timeout) * time.Second): - err = fmt.Errorf("timed out") - } - - duration := float64(time.Now().Sub(start)) / float64(time.Second) - - consoleLock.Lock() - if err != nil { - fmt.Printf("%s>>> Test %s finished after %0.1f secs with error: %v%s\n", fail, t.name, duration, err, reset) - } else { - fmt.Printf("%s>>> Test %s finished with success after %0.1f secs%s\n", succ, t.name, duration, reset) - } - if err != nil || verbose { - fmt.Print(out.String()) - fmt.Println() - } - consoleLock.Unlock() - - if err != nil && useScheduler { - updateScheduler(t.name, duration) - } - - return err != nil -} - -func updateScheduler(test string, duration float64) { - req := &http.Request{ - Method: "POST", - Host: schedulerHost, - URL: &url.URL{ - Opaque: fmt.Sprintf("/record/%s/%0.2f", url.QueryEscape(test), duration), - Scheme: "http", - Host: schedulerHost, - }, - Close: true, - } - if resp, err := http.DefaultClient.Do(req); err != nil { - fmt.Printf("Error updating scheduler: %v\n", err) - } else { - resp.Body.Close() - } -} - -func getSchedule(tests []string) ([]string, error) { - var ( - userName = os.Getenv("CIRCLE_PROJECT_USERNAME") - project = os.Getenv("CIRCLE_PROJECT_REPONAME") - buildNum = os.Getenv("CIRCLE_BUILD_NUM") - testRun = userName + "-" + project + "-integration-" + buildNum - shardCount = os.Getenv("CIRCLE_NODE_TOTAL") - shardID = os.Getenv("CIRCLE_NODE_INDEX") - requestBody = &bytes.Buffer{} - ) - if err := json.NewEncoder(requestBody).Encode(schedule{tests}); err != nil { - return []string{}, err - } - url := fmt.Sprintf("http://%s/schedule/%s/%s/%s", schedulerHost, testRun, shardCount, shardID) - resp, err := http.Post(url, jsonContentType, requestBody) - if err != nil { - return []string{}, err - } - var sched schedule - if err := json.NewDecoder(resp.Body).Decode(&sched); err != nil { - return []string{}, err - } - return sched.Tests, nil -} - -func getTests(testNames []string) (tests, error) { - var err error - if useScheduler { - testNames, err = getSchedule(testNames) - if err != nil { - return tests{}, err - } - } - tests := tests{} - for _, name := range testNames { - parts := strings.Split(strings.TrimSuffix(name, "_test.sh"), "_") - numHosts, err := strconv.Atoi(parts[len(parts)-1]) - if err != nil { - numHosts = 1 - } - tests = append(tests, test{name, numHosts}) - fmt.Printf("Test %s needs %d hosts\n", name, numHosts) - } - return tests, nil -} - -func summary(tests, failed tests) { - if len(failed) > 0 { - fmt.Printf("%s>>> Ran %d tests, %d failed%s\n", fail, len(tests), len(failed), reset) - for _, test := range failed { - fmt.Printf("%s>>> Fail %s%s\n", fail, test.name, reset) - } - } else { - fmt.Printf("%s>>> Ran %d tests, all succeeded%s\n", succ, len(tests), reset) - } -} - -func parallel(ts tests, hosts []string) bool { - testsCopy := ts - sort.Sort(sort.Reverse(ts)) - resultsChan := make(chan result) - outstanding := 0 - failed := tests{} - for len(ts) > 0 || outstanding > 0 { - // While we have some free hosts, try and schedule - // a test on them - for len(hosts) > 0 { - test, ok := ts.pick(len(hosts)) - if !ok { - break - } - testHosts := hosts[:test.hosts] - hosts = hosts[test.hosts:] - - go func() { - errored := test.run(testHosts) - resultsChan <- result{test, errored, testHosts} - }() - outstanding++ - } - - // Otherwise, wait for the test to finish and return - // the hosts to the pool - result := <-resultsChan - hosts = append(hosts, result.hosts...) - outstanding-- - if result.errored { - failed = append(failed, result.test) - } - } - summary(testsCopy, failed) - return len(failed) > 0 -} - -func sequential(ts tests, hosts []string) bool { - failed := tests{} - for _, test := range ts { - if test.run(hosts) { - failed = append(failed, test) - } - } - summary(ts, failed) - return len(failed) > 0 -} - -func main() { - mflag.BoolVar(&useScheduler, []string{"scheduler"}, false, "Use scheduler to distribute tests across shards") - mflag.BoolVar(&runParallel, []string{"parallel"}, false, "Run tests in parallel on hosts where possible") - mflag.BoolVar(&verbose, []string{"v"}, false, "Print output from all tests (Also enabled via DEBUG=1)") - mflag.StringVar(&schedulerHost, []string{"scheduler-host"}, defaultSchedulerHost, "Hostname of scheduler.") - mflag.IntVar(&timeout, []string{"timeout"}, 180, "Max time to run one test for, in seconds") - mflag.Parse() - - if len(os.Getenv("DEBUG")) > 0 { - verbose = true - } - - testArgs := mflag.Args() - tests, err := getTests(testArgs) - if err != nil { - fmt.Printf("Error parsing tests: %v (%v)\n", err, testArgs) - os.Exit(1) - } - - hosts := strings.Fields(os.Getenv("HOSTS")) - maxHosts := len(hosts) - if maxHosts == 0 { - fmt.Print("No HOSTS specified.\n") - os.Exit(1) - } - - var errored bool - if runParallel { - errored = parallel(tests, hosts) - } else { - errored = sequential(tests, hosts) - } - - if errored { - os.Exit(1) - } -} diff --git a/tools/sched b/tools/sched deleted file mode 100755 index a282558f..00000000 --- a/tools/sched +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env python -import sys, string, urllib -import requests -import optparse - -def test_time(target, test_name, runtime): - r = requests.post(target + "/record/%s/%f" % (urllib.quote(test_name, safe=""), runtime)) - print r.text.encode('utf-8') - assert r.status_code == 204 - -def test_sched(target, test_run, shard_count, shard_id): - tests = {'tests': string.split(sys.stdin.read())} - r = requests.post(target + "/schedule/%s/%d/%d" % (test_run, shard_count, shard_id), json=tests) - assert r.status_code == 200 - result = r.json() - for test in sorted(result['tests']): - print test.encode('utf-8') - -def usage(): - print "%s (--target=...) " % sys.argv[0] - print " time " - print " sched " - -def main(): - parser = optparse.OptionParser() - parser.add_option('--target', default="http://positive-cocoa-90213.appspot.com") - options, args = parser.parse_args() - if len(args) < 3: - usage() - sys.exit(1) - - if args[0] == "time": - test_time(options.target, args[1], float(args[2])) - elif args[0] == "sched": - test_sched(options.target, args[1], int(args[2]), int(args[3])) - else: - usage() - -if __name__ == '__main__': - main() diff --git a/tools/scheduler/.gitignore b/tools/scheduler/.gitignore deleted file mode 100644 index a65b4177..00000000 --- a/tools/scheduler/.gitignore +++ /dev/null @@ -1 +0,0 @@ -lib diff --git a/tools/scheduler/README.md b/tools/scheduler/README.md deleted file mode 100644 index 8489d787..00000000 --- a/tools/scheduler/README.md +++ /dev/null @@ -1,6 +0,0 @@ -To upload newer version: - -``` -pip install -r requirements.txt -t lib -appcfg.py update . -``` diff --git a/tools/scheduler/app.yaml b/tools/scheduler/app.yaml deleted file mode 100644 index 21f5f052..00000000 --- a/tools/scheduler/app.yaml +++ /dev/null @@ -1,15 +0,0 @@ -application: positive-cocoa-90213 -version: 1 -runtime: python27 -api_version: 1 -threadsafe: true - -handlers: -- url: .* - script: main.app - -libraries: -- name: webapp2 - version: latest -- name: ssl - version: latest diff --git a/tools/scheduler/appengine_config.py b/tools/scheduler/appengine_config.py deleted file mode 100644 index f4489ff9..00000000 --- a/tools/scheduler/appengine_config.py +++ /dev/null @@ -1,3 +0,0 @@ -from google.appengine.ext import vendor - -vendor.add('lib') diff --git a/tools/scheduler/cron.yaml b/tools/scheduler/cron.yaml deleted file mode 100644 index 652aed80..00000000 --- a/tools/scheduler/cron.yaml +++ /dev/null @@ -1,4 +0,0 @@ -cron: -- description: periodic gc - url: /tasks/gc - schedule: every 5 minutes diff --git a/tools/scheduler/main.py b/tools/scheduler/main.py deleted file mode 100644 index 3b540b54..00000000 --- a/tools/scheduler/main.py +++ /dev/null @@ -1,206 +0,0 @@ -import collections -import json -import logging -import operator -import re - -import flask -from oauth2client.client import GoogleCredentials -from googleapiclient import discovery - -from google.appengine.api import urlfetch -from google.appengine.ext import ndb - -app = flask.Flask('scheduler') -app.debug = True - -# We use exponential moving average to record -# test run times. Higher alpha discounts historic -# observations faster. -alpha = 0.3 - - -class Test(ndb.Model): - total_run_time = ndb.FloatProperty(default=0.) # Not total, but a EWMA - total_runs = ndb.IntegerProperty(default=0) - - def parallelism(self): - name = self.key.string_id() - m = re.search('(\d+)_test.sh$', name) - if m is None: - return 1 - else: - return int(m.group(1)) - - def cost(self): - p = self.parallelism() - logging.info("Test %s has parallelism %d and avg run time %s", - self.key.string_id(), p, self.total_run_time) - return self.parallelism() * self.total_run_time - - -class Schedule(ndb.Model): - shards = ndb.JsonProperty() - - -@app.route('/record//', methods=['POST']) -@ndb.transactional -def record(test_name, runtime): - test = Test.get_by_id(test_name) - if test is None: - test = Test(id=test_name) - test.total_run_time = (test.total_run_time * - (1 - alpha)) + (float(runtime) * alpha) - test.total_runs += 1 - test.put() - return ('', 204) - - -@app.route( - '/schedule///', methods=['POST']) -def schedule(test_run, shard_count, shard): - # read tests from body - test_names = flask.request.get_json(force=True)['tests'] - - # first see if we have a scedule already - schedule_id = "%s-%d" % (test_run, shard_count) - schedule = Schedule.get_by_id(schedule_id) - if schedule is not None: - return flask.json.jsonify(tests=schedule.shards[str(shard)]) - - # if not, do simple greedy algorithm - test_times = ndb.get_multi( - ndb.Key(Test, test_name) for test_name in test_names) - - def avg(test): - if test is not None: - return test.cost() - return 1 - - test_times = [(test_name, avg(test)) - for test_name, test in zip(test_names, test_times)] - test_times_dict = dict(test_times) - test_times.sort(key=operator.itemgetter(1)) - - shards = {i: [] for i in xrange(shard_count)} - while test_times: - test_name, time = test_times.pop() - - # find shortest shard and put it in that - s, _ = min( - ((i, sum(test_times_dict[t] for t in shards[i])) - for i in xrange(shard_count)), - key=operator.itemgetter(1)) - - shards[s].append(test_name) - - # atomically insert or retrieve existing schedule - schedule = Schedule.get_or_insert(schedule_id, shards=shards) - return flask.json.jsonify(tests=schedule.shards[str(shard)]) - - -FIREWALL_REGEXES = [ - re.compile( - r'^(?P\w+)-allow-(?P\w+)-(?P\d+)-(?P\d+)$' - ), - re.compile(r'^(?P\w+)-(?P\d+)-(?P\d+)-allow-' - r'(?P[\w\-]+)$'), -] -NAME_REGEXES = [ - re.compile(r'^host(?P\d+)-(?P\d+)-(?P\d+)$'), - re.compile(r'^test-(?P\d+)-(?P\d+)-(?P\d+)$'), -] - - -def _matches_any_regex(name, regexes): - for regex in regexes: - matches = regex.match(name) - if matches: - return matches - - -PROJECTS = [ - ('weaveworks/weave', 'weave-net-tests', 'us-central1-a', True), - ('weaveworks/weave', 'positive-cocoa-90213', 'us-central1-a', True), - ('weaveworks/scope', 'scope-integration-tests', 'us-central1-a', False), -] - - -@app.route('/tasks/gc') -def gc(): - # Get list of running VMs, pick build id out of VM name - credentials = GoogleCredentials.get_application_default() - compute = discovery.build('compute', 'v1', credentials=credentials) - - for repo, project, zone, gc_fw in PROJECTS: - gc_project(compute, repo, project, zone, gc_fw) - - return "Done" - - -def gc_project(compute, repo, project, zone, gc_fw): - logging.info("GCing %s, %s, %s", repo, project, zone) - # Get list of builds, filter down to running builds: - running = _get_running_builds(repo) - # Stop VMs for builds that aren't running: - _gc_compute_engine_instances(compute, project, zone, running) - # Remove firewall rules for builds that aren't running: - if gc_fw: - _gc_firewall_rules(compute, project, running) - - -def _get_running_builds(repo): - result = urlfetch.fetch( - 'https://circleci.com/api/v1/project/%s' % repo, - headers={'Accept': 'application/json'}) - assert result.status_code == 200 - builds = json.loads(result.content) - running = { - build['build_num'] - for build in builds if not build.get('stop_time') - } - logging.info("Runnings builds: %r", running) - return running - - -def _get_hosts_by_build(instances): - host_by_build = collections.defaultdict(list) - for instance in instances['items']: - matches = _matches_any_regex(instance['name'], NAME_REGEXES) - if not matches: - continue - host_by_build[int(matches.group('build'))].append(instance['name']) - logging.info("Running VMs by build: %r", host_by_build) - return host_by_build - - -def _gc_compute_engine_instances(compute, project, zone, running): - instances = compute.instances().list(project=project, zone=zone).execute() - if 'items' not in instances: - return - host_by_build = _get_hosts_by_build(instances) - stopped = [] - for build, names in host_by_build.iteritems(): - if build in running: - continue - for name in names: - stopped.append(name) - logging.info("Stopping VM %s", name) - compute.instances().delete( - project=project, zone=zone, instance=name).execute() - return stopped - - -def _gc_firewall_rules(compute, project, running): - firewalls = compute.firewalls().list(project=project).execute() - if 'items' not in firewalls: - return - for firewall in firewalls['items']: - matches = _matches_any_regex(firewall['name'], FIREWALL_REGEXES) - if not matches: - continue - if int(matches.group('build')) in running: - continue - logging.info("Deleting firewall rule %s", firewall['name']) - compute.firewalls().delete( - project=project, firewall=firewall['name']).execute() diff --git a/tools/scheduler/requirements.txt b/tools/scheduler/requirements.txt deleted file mode 100644 index d4d47e6e..00000000 --- a/tools/scheduler/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -flask -google-api-python-client diff --git a/tools/shell-lint b/tools/shell-lint deleted file mode 100755 index fffbb8b1..00000000 --- a/tools/shell-lint +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash -# -# Lint all shell files in given directories with `shellcheck`. -# -# e.g. -# $ shell-lint infra k8s -# -# Depends on: -# - shellcheck -# - files-with-type -# - file >= 5.22 - -"$(dirname "${BASH_SOURCE[0]}")/files-with-type" text/x-shellscript "$@" | xargs --no-run-if-empty shellcheck diff --git a/tools/socks/Dockerfile b/tools/socks/Dockerfile deleted file mode 100644 index 867cd6bc..00000000 --- a/tools/socks/Dockerfile +++ /dev/null @@ -1,7 +0,0 @@ -FROM gliderlabs/alpine -MAINTAINER Weaveworks Inc -WORKDIR / -COPY proxy / -EXPOSE 8000 -EXPOSE 8080 -ENTRYPOINT ["/proxy"] diff --git a/tools/socks/Makefile b/tools/socks/Makefile deleted file mode 100644 index 2daeda64..00000000 --- a/tools/socks/Makefile +++ /dev/null @@ -1,29 +0,0 @@ -.PHONY: all clean - -IMAGE_TAR=image.tar -IMAGE_NAME=weaveworks/socksproxy -PROXY_EXE=proxy -NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \ - rm $@; \ - echo "\nYour go standard library was built without the 'netgo' build tag."; \ - echo "To fix that, run"; \ - echo " sudo go clean -i net"; \ - echo " sudo go install -tags netgo std"; \ - false; \ -} - -all: $(IMAGE_TAR) - -$(IMAGE_TAR): Dockerfile $(PROXY_EXE) - docker build -t $(IMAGE_NAME) . - docker save $(IMAGE_NAME):latest > $@ - -$(PROXY_EXE): *.go - go get -tags netgo ./$(@D) - go build -ldflags "-extldflags \"-static\" -linkmode=external" -tags netgo -o $@ ./$(@D) - $(NETGO_CHECK) - -clean: - -docker rmi $(IMAGE_NAME) - rm -rf $(PROXY_EXE) $(IMAGE_TAR) - go clean ./... diff --git a/tools/socks/README.md b/tools/socks/README.md deleted file mode 100644 index 596c2b32..00000000 --- a/tools/socks/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# SOCKS Proxy - -The challenge: you’ve built and deployed your microservices based -application on a Weave network, running on a set of VMs on EC2. Many -of the services’ public API are reachable from the internet via an -Nginx-based reverse proxy, but some of the services also expose -private monitoring and manage endpoints via embedded HTTP servers. -How do I securely get access to these from my laptop, without exposing -them to the world? - -One method we’ve started using at Weaveworks is a 90’s technology - a -SOCKS proxy combined with a PAC script. It’s relatively -straight-forward: one ssh’s into any of the VMs participating in the -Weave network, starts the SOCKS proxy in a container on Weave the -network, and SSH port forwards a few local port to the proxy. All -that’s left is for the user to configure his browser to use the proxy, -and voila, you can now access your Docker containers, via the Weave -network (and with all the magic of weavedns), from your laptop’s -browser! - -It is perhaps worth noting there is nothing Weave-specific about this -approach - this should work with any SDN or private network. - -A quick example: - -``` -vm1$ weave launch -vm1$ eval $(weave env) -vm1$ docker run -d --name nginx nginx -``` - -And on your laptop - -``` -laptop$ git clone https://github.com/weaveworks/tools -laptop$ cd tools/socks -laptop$ ./connect.sh vm1 -Starting proxy container... -Please configure your browser for proxy -http://localhost:8080/proxy.pac -``` - -To configure your Mac to use the proxy: - -1. Open System Preferences -2. Select Network -3. Click the 'Advanced' button -4. Select the Proxies tab -5. Click the 'Automatic Proxy Configuration' check box -6. Enter 'http://localhost:8080/proxy.pac' in the URL box -7. Remove `*.local` from the 'Bypass proxy settings for these Hosts & Domains' - -Now point your browser at http://nginx.weave.local/ diff --git a/tools/socks/connect.sh b/tools/socks/connect.sh deleted file mode 100755 index 7e7975a3..00000000 --- a/tools/socks/connect.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -set -eu - -if [ $# -ne 1 ]; then - echo "Usage: $0 " - exit 1 -fi - -HOST=$1 - -echo "Starting proxy container..." -PROXY_CONTAINER=$(ssh "$HOST" weave run -d weaveworks/socksproxy) - -function finish() { - echo "Removing proxy container.." - # shellcheck disable=SC2029 - ssh "$HOST" docker rm -f "$PROXY_CONTAINER" -} -trap finish EXIT - -# shellcheck disable=SC2029 -PROXY_IP=$(ssh "$HOST" -- "docker inspect --format='{{.NetworkSettings.IPAddress}}' $PROXY_CONTAINER") -echo 'Please configure your browser for proxy http://localhost:8080/proxy.pac' -# shellcheck disable=SC2029 -ssh "-L8000:$PROXY_IP:8000" "-L8080:$PROXY_IP:8080" "$HOST" docker attach "$PROXY_CONTAINER" diff --git a/tools/socks/main.go b/tools/socks/main.go deleted file mode 100644 index 7cd8c708..00000000 --- a/tools/socks/main.go +++ /dev/null @@ -1,97 +0,0 @@ -package main - -import ( - "fmt" - "net" - "net/http" - "os" - "strings" - "text/template" - - socks5 "github.com/armon/go-socks5" - "github.com/weaveworks/common/mflag" - "github.com/weaveworks/common/mflagext" - "golang.org/x/net/context" -) - -type pacFileParameters struct { - HostMatch string - Aliases map[string]string -} - -const ( - pacfile = ` -function FindProxyForURL(url, host) { - if(shExpMatch(host, "{{.HostMatch}}")) { - return "SOCKS5 localhost:8000"; - } - {{range $key, $value := .Aliases}} - if (host == "{{$key}}") { - return "SOCKS5 localhost:8000"; - } - {{end}} - return "DIRECT"; -} -` -) - -func main() { - var ( - as []string - hostMatch string - ) - mflagext.ListVar(&as, []string{"a", "-alias"}, []string{}, "Specify hostname aliases in the form alias:hostname. Can be repeated.") - mflag.StringVar(&hostMatch, []string{"h", "-host-match"}, "*.weave.local", "Specify main host shExpMatch expression in pacfile") - mflag.Parse() - - var aliases = map[string]string{} - for _, a := range as { - parts := strings.SplitN(a, ":", 2) - if len(parts) != 2 { - fmt.Printf("'%s' is not a valid alias.\n", a) - mflag.Usage() - os.Exit(1) - } - aliases[parts[0]] = parts[1] - } - - go socksProxy(aliases) - - t := template.Must(template.New("pacfile").Parse(pacfile)) - http.HandleFunc("/proxy.pac", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/x-ns-proxy-autoconfig") - t.Execute(w, pacFileParameters{hostMatch, aliases}) - }) - - if err := http.ListenAndServe(":8080", nil); err != nil { - panic(err) - } -} - -type aliasingResolver struct { - aliases map[string]string - socks5.NameResolver -} - -func (r aliasingResolver) Resolve(ctx context.Context, name string) (context.Context, net.IP, error) { - if alias, ok := r.aliases[name]; ok { - return r.NameResolver.Resolve(ctx, alias) - } - return r.NameResolver.Resolve(ctx, name) -} - -func socksProxy(aliases map[string]string) { - conf := &socks5.Config{ - Resolver: aliasingResolver{ - aliases: aliases, - NameResolver: socks5.DNSResolver{}, - }, - } - server, err := socks5.New(conf) - if err != nil { - panic(err) - } - if err := server.ListenAndServe("tcp", ":8000"); err != nil { - panic(err) - } -} diff --git a/tools/test b/tools/test deleted file mode 100755 index c87bdd07..00000000 --- a/tools/test +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/bash - -set -e - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SLOW= -NO_GO_GET=true -TAGS= -PARALLEL= -RACE="-race -covermode=atomic" -TIMEOUT=1m - -usage() { - echo "$0 [-slow] [-in-container foo] [-netgo] [-(no-)go-get] [-timeout 1m]" -} - -while [ $# -gt 0 ]; do - case "$1" in - "-slow") - SLOW=true - shift 1 - ;; - "-no-race") - RACE= - shift 1 - ;; - "-no-go-get") - NO_GO_GET=true - shift 1 - ;; - "-go-get") - NO_GO_GET= - shift 1 - ;; - "-netgo") - TAGS="netgo" - shift 1 - ;; - "-tags") - TAGS="$2" - shift 2 - ;; - "-p") - PARALLEL=true - shift 1 - ;; - "-timeout") - TIMEOUT=$2 - shift 2 - ;; - *) - usage - exit 2 - ;; - esac -done - -GO_TEST_ARGS=(-tags "${TAGS[@]}" -cpu 4 -timeout $TIMEOUT) - -if [ -n "$SLOW" ] || [ -n "$CIRCLECI" ]; then - SLOW=true -fi - -if [ -n "$SLOW" ]; then - GO_TEST_ARGS=("${GO_TEST_ARGS[@]}" ${RACE}) - - # shellcheck disable=SC2153 - if [ -n "$COVERDIR" ]; then - coverdir="$COVERDIR" - else - coverdir=$(mktemp -d coverage.XXXXXXXXXX) - fi - - mkdir -p "$coverdir" -fi - -fail=0 - -if [ -z "$TESTDIRS" ]; then - # NB: Relies on paths being prefixed with './'. - TESTDIRS=($(git ls-files -- '*_test.go' | grep -vE '^(vendor|experimental)/' | xargs -n1 dirname | sort -u | sed -e 's|^|./|')) -else - # TESTDIRS on the right side is not really an array variable, it - # is just a string with spaces, but it is written like that to - # shut up the shellcheck tool. - TESTDIRS=($(for d in ${TESTDIRS[*]}; do echo "$d"; done)) -fi - -# If running on circle, use the scheduler to work out what tests to run on what shard -if [ -n "$CIRCLECI" ] && [ -z "$NO_SCHEDULER" ] && [ -x "$DIR/sched" ]; then - PREFIX=$(go list -e ./ | sed -e 's/\//-/g') - TESTDIRS=($(echo "${TESTDIRS[@]}" | "$DIR/sched" sched "$PREFIX-$CIRCLE_PROJECT_USERNAME-$CIRCLE_PROJECT_REPONAME-$CIRCLE_BUILD_NUM" "$CIRCLE_NODE_TOTAL" "$CIRCLE_NODE_INDEX")) - echo "${TESTDIRS[@]}" -fi - -PACKAGE_BASE=$(go list -e ./) - -# Speed up the tests by compiling and installing their dependencies first. -go test -i "${GO_TEST_ARGS[@]}" "${TESTDIRS[@]}" - -run_test() { - local dir=$1 - if [ -z "$NO_GO_GET" ]; then - go get -t -tags "${TAGS[@]}" "$dir" - fi - - local GO_TEST_ARGS_RUN=("${GO_TEST_ARGS[@]}") - if [ -n "$SLOW" ]; then - local COVERPKGS - COVERPKGS=$( ( - go list "$dir" - go list -f '{{join .Deps "\n"}}' "$dir" | grep -v "vendor" | grep "^$PACKAGE_BASE/" - ) | paste -s -d, -) - local output - output=$(mktemp "$coverdir/unit.XXXXXXXXXX") - local GO_TEST_ARGS_RUN=("${GO_TEST_ARGS[@]}" -coverprofile=$output -coverpkg=$COVERPKGS) - fi - - local START - START=$(date +%s) - if ! go test "${GO_TEST_ARGS_RUN[@]}" "$dir"; then - fail=1 - fi - local END - END=$(date +%s) - local RUNTIME=$((END - START)) - - # Report test runtime when running on circle, to help scheduler - if [ -n "$CIRCLECI" ] && [ -z "$NO_SCHEDULER" ] && [ -x "$DIR/sched" ]; then - "$DIR/sched" time "$dir" "$RUNTIME" - fi -} - -for dir in "${TESTDIRS[@]}"; do - if [ -n "$PARALLEL" ]; then - run_test "$dir" & - else - run_test "$dir" - fi -done - -if [ -n "$PARALLEL" ]; then - wait -fi - -if [ -n "$SLOW" ] && [ -z "$COVERDIR" ]; then - go get github.com/weaveworks/tools/cover - cover "$coverdir"/* >profile.cov - rm -rf "$coverdir" - go tool cover -html=profile.cov -o=coverage.html - go tool cover -func=profile.cov | tail -n1 -fi - -exit $fail From 6d81f8b039e7475225bdc482d7a059b0e1c80698 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 5 May 2020 16:29:22 +0200 Subject: [PATCH 112/403] remove copyright notice, the file in question is gone --- COPYING.LGPL-3 | 175 ------------------------------------------------- 1 file changed, 175 deletions(-) delete mode 100644 COPYING.LGPL-3 diff --git a/COPYING.LGPL-3 b/COPYING.LGPL-3 deleted file mode 100644 index 89c7d69e..00000000 --- a/COPYING.LGPL-3 +++ /dev/null @@ -1,175 +0,0 @@ -./tools/integration/assert.sh is a copy of - - https://github.com/lehmannro/assert.sh/blob/master/assert.sh - -Since it was imported from its original source, it has only received -cosmetic modifications. As it is licensed under the LGPL-3, here's the -license text in its entirety: - - - - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. From 4b00a6248e9930cc30fc79e416dd2ef0377ee1d8 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Mon, 11 May 2020 10:36:09 +0100 Subject: [PATCH 113/403] Fixed up ID and hiding for Average Terms aggregation --- grafanalib/elasticsearch.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 0753f592..5e921cf8 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -72,6 +72,8 @@ class AverageMetricAgg(object): """ field = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=0, validator=instance_of(int)) + hide = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): return { From 2a249979db5733a31e229cf7e99f5de20ef50c42 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 11 May 2020 15:02:52 +0200 Subject: [PATCH 114/403] document changes of 0.5.7 --- CHANGELOG.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1fc05747..47d72d32 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,13 +2,17 @@ Changelog ========= -0.5.7 (TBD) +0.5.7 (2020-05-11) =========== Changes ------- -* <...> +* Fix crasher instatiating elasticsearch panels. +* Remove unused ``tools/`` directory. + +Thanks a lot for your contributions to this release, @DWalker487, @dholbach and @matthewmrichter. + 0.5.6 (2020-05-05) ================== From 967518de571d4257caca9d5d9762574547d94e72 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 11 May 2020 15:03:08 +0200 Subject: [PATCH 115/403] version bump for 0.5.7 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cddc2576..c658d9f8 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.6', + version='0.5.7', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From ca6a1fbfff037a5ee7c60b0141acd0abc0d581e6 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 11 May 2020 15:09:06 +0200 Subject: [PATCH 116/403] fix documentation --- docs/releasing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releasing.rst b/docs/releasing.rst index ea7fa4f3..cf364931 100644 --- a/docs/releasing.rst +++ b/docs/releasing.rst @@ -16,7 +16,7 @@ Smoke-testing .. code-block:: console - $ python setup.py install + $ python setup.py install --user * Check ``~/.local/bin/generate-dashboard`` for the update version. * Try the example on `README `_. From 472ef220796eb4059bbe80b9fc96b1464d26ae34 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 11 May 2020 15:19:21 +0200 Subject: [PATCH 117/403] open 0.5.8 for development --- CHANGELOG.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 47d72d32..eb9e8dc5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,16 @@ Changelog ========= + +0.5.8 (TBD) +=========== + +Changes +------- + +* ... + + 0.5.7 (2020-05-11) =========== From f86df8f277c52b9c28e7d14eb389bdb73cd0a660 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Thu, 14 May 2020 11:32:54 +0100 Subject: [PATCH 118/403] Added bucket script aggregation --- grafanalib/elasticsearch.py | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 5e921cf8..9d8a3d1e 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -141,6 +141,42 @@ def to_json_data(self): } +@attr.s +class BucketScriptAgg(object): + """An aggregator that applies a bucket script to the results of previous aggregations. + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-bucket-script-aggregation.html + + :param fields: dictionary of field names mapped to aggregation IDs to be used in the bucket script + e.g. { "field1":1 }, which allows the output of aggregate ID 1 to be referenced as + params.field1 in the bucket script + :param script: script to apply to the data using the variables specified in 'fields' + :param id: id of the aggregator + :param hide: show/hide the metric in the final panel display + """ + fields = attr.ib(default={}, validator=instance_of(dict)) + id = attr.ib(default=0, validator=instance_of(int)) + hide = attr.ib(default=False, validator=instance_of(bool)) + script = attr.ib(default="", validator=instance_of(str)) + + def to_json_data(self): + pipelineVars = [] + for field in self.fields: + pipelineVars.append({ + "name": str(field), + "pipelineAgg": str(self.fields[field]) + }) + + return { + 'type': 'bucket_script', + 'id': str(self.id), + 'hide': self.hide, + 'pipelineVariables': pipelineVars, + 'settings': { + 'script': self.script + }, + } + + @attr.s class Filter(object): """ A Filter for a FilterGroupBy aggregator. From 4351972d2f5763cd33eba60e9d0097dd209941a5 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Thu, 14 May 2020 11:37:06 +0100 Subject: [PATCH 119/403] Updated changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index eb9e8dc5..299c8cd8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ Changelog Changes ------- +* Add Elasticsearch bucket script pipeline aggregator * ... From 8550aeb3f5a2ce6d63f1f0910bc70908d8d3224e Mon Sep 17 00:00:00 2001 From: Duncan Walker Date: Thu, 14 May 2020 15:21:56 +0100 Subject: [PATCH 120/403] Whitespace fixes --- grafanalib/elasticsearch.py | 1 - 1 file changed, 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 326784ee..eae408ce 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -20,7 +20,6 @@ class CountMetricAgg(object): """ hide = attr.ib(default=False, validator=instance_of(bool)) - def to_json_data(self): return { 'hide': self.hide, From 71508deb0cb6c11c127136cbffca1954ed878e9b Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Tue, 26 May 2020 10:44:12 +0100 Subject: [PATCH 121/403] Fixed merge issue, test pass locally --- grafanalib/elasticsearch.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 4a1eae69..9badd27e 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -21,7 +21,7 @@ class CountMetricAgg(object): """ id = attr.ib(default=0, validator=instance_of(int)) hide = attr.ib(default=False, validator=instance_of(bool)) - + def to_json_data(self): return { 'id': str(self.id), @@ -45,7 +45,7 @@ class MaxMetricAgg(object): field = attr.ib(default="", validator=instance_of(str)) id = attr.ib(default=0, validator=instance_of(int)) hide = attr.ib(default=False, validator=instance_of(bool)) - + def to_json_data(self): return { 'id': str(self.id), @@ -69,7 +69,7 @@ class CardinalityMetricAgg(object): field = attr.ib(default="", validator=instance_of(str)) id = attr.ib(default=0, validator=instance_of(int)) hide = attr.ib(default=False, validator=instance_of(bool)) - + def to_json_data(self): return { 'id': str(self.id), @@ -136,8 +136,10 @@ def to_json_data(self): 'type': 'derivative', 'field': self.field, 'settings': settings, + } +@attr.s class SumMetricAgg(object): """An aggregator that provides the sum of the values. https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html From 316cf73b3172dc5af5b1780382b4c9e093c085c4 Mon Sep 17 00:00:00 2001 From: Duncan Martin Walker Date: Wed, 27 May 2020 10:18:50 +0100 Subject: [PATCH 122/403] Fix indentation linting issue --- grafanalib/elasticsearch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 9badd27e..b9275af2 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -136,7 +136,7 @@ def to_json_data(self): 'type': 'derivative', 'field': self.field, 'settings': settings, - } + } @attr.s From 333c2a41eb0aedbaf5d8917223f28c7d85f8331b Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Wed, 27 May 2020 12:42:32 +0200 Subject: [PATCH 123/403] Remove links to singlestat for now, so we can continue to land changes. This is not a fix for #251. --- grafanalib/core.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index a150b553..dcdea21b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1243,15 +1243,12 @@ def to_json_data(self): class SingleStat(object): """Generates Single Stat panel json structure - Grafana doc on singlestat: http://docs.grafana.org/reference/singlestat/ - :param dataSource: Grafana datasource name :param targets: list of metric requests for chosen datasource :param title: panel title :param cacheTimeout: metric query result cache ttl :param colors: the list of colors that can be used for coloring - panel value or background. Additional info on coloring in docs: - http://docs.grafana.org/reference/singlestat/#coloring + panel value or background. :param colorBackground: defines if grafana will color panel background :param colorValue: defines if grafana will color panel value :param description: optional panel description @@ -1265,8 +1262,6 @@ class SingleStat(object): :param interval: defines time interval between metric queries :param links: additional web links :param mappingType: defines panel mapping type. - Additional info can be found in docs: - http://docs.grafana.org/reference/singlestat/#value-to-text-mapping :param mappingTypes: the list of available mapping types for panel :param maxDataPoints: maximum metric query results, that will be used for rendering @@ -1280,8 +1275,6 @@ class SingleStat(object): :param rangeMaps: the list of value to text mappings :param span: defines the number of spans that will be used for panel :param sparkline: defines if grafana should draw an additional sparkline. - Sparkline grafana documentation: - http://docs.grafana.org/reference/singlestat/#spark-lines :param thresholds: single stat thresholds :param transparent: defines if panel should be transparent :param valueFontSize: defines value font size From 2da726bdc99e594152bb3761ed95ccce659a7a57 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Wed, 27 May 2020 17:44:45 +0200 Subject: [PATCH 124/403] Readd links to singlestat docs (now Stat) fixes: #251 --- grafanalib/core.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index dcdea21b..d9fc924a 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1243,12 +1243,15 @@ def to_json_data(self): class SingleStat(object): """Generates Single Stat panel json structure + Grafana doc on singlestat: https://grafana.com/docs/grafana/latest/features/panels/singlestat/ + :param dataSource: Grafana datasource name :param targets: list of metric requests for chosen datasource :param title: panel title :param cacheTimeout: metric query result cache ttl :param colors: the list of colors that can be used for coloring - panel value or background. + panel value or background. Additional info on coloring in docs: + https://grafana.com/docs/grafana/latest/features/panels/singlestat/#coloring :param colorBackground: defines if grafana will color panel background :param colorValue: defines if grafana will color panel value :param description: optional panel description @@ -1262,6 +1265,8 @@ class SingleStat(object): :param interval: defines time interval between metric queries :param links: additional web links :param mappingType: defines panel mapping type. + Additional info can be found in docs: + https://grafana.com/docs/grafana/latest/features/panels/singlestat/#value-to-text-mapping :param mappingTypes: the list of available mapping types for panel :param maxDataPoints: maximum metric query results, that will be used for rendering @@ -1275,6 +1280,8 @@ class SingleStat(object): :param rangeMaps: the list of value to text mappings :param span: defines the number of spans that will be used for panel :param sparkline: defines if grafana should draw an additional sparkline. + Sparkline grafana documentation: + https://grafana.com/docs/grafana/latest/features/panels/singlestat/#spark-lines :param thresholds: single stat thresholds :param transparent: defines if panel should be transparent :param valueFontSize: defines value font size From 62d88af2e1427be6b478dbed26c3c9a95b620e87 Mon Sep 17 00:00:00 2001 From: Faustin Lammler Date: Wed, 17 Jun 2020 14:14:36 +0200 Subject: [PATCH 125/403] Curl default behavior is to STDOUT content Curl on linux by default output the content of the downloaded file on the STDOUT (you are maybe using macosx?). Add the `-o` option and the name of the file to create. Using `wget` maybe more portable? --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c8eded9e..de4cecba 100644 --- a/README.rst +++ b/README.rst @@ -47,7 +47,7 @@ Generate the JSON dashboard like so: .. code-block:: console - $ curl https://raw.githubusercontent.com/weaveworks/grafanalib/master/grafanalib/tests/examples/example.dashboard.py + $ curl -o example.dashboard.py https://raw.githubusercontent.com/weaveworks/grafanalib/master/grafanalib/tests/examples/example.dashboard.py $ generate-dashboard -o frontend.json example.dashboard.py From 0626e8e566c4ba73cb21aa2006b8e74ca5b4a77b Mon Sep 17 00:00:00 2001 From: James Gibson Date: Wed, 1 Jul 2020 10:38:24 +0100 Subject: [PATCH 126/403] Add SVG class for the SVG panel --- CHANGELOG.rst | 1 + grafanalib/core.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 473469c6..0970e73c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Changes * Add Elasticsearch bucket script pipeline aggregator * Added ability to hide metrics for Elasticsearch MetricAggs * Add derivative metric aggregation for Elasticsearch +* Add ``SVG`` class to support the SVG panel * ... diff --git a/grafanalib/core.py b/grafanalib/core.py index d9fc924a..17e2ef8e 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -79,6 +79,7 @@ def to_json_data(self): GAUGE_TYPE = "gauge" HEATMAP_TYPE = "heatmap" STATUSMAP_TYPE = "flant-statusmap-panel" +SVG_TYPE = 'marcuscalidus-svg-panel' DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -2208,3 +2209,73 @@ def to_json_data(self): if self.alert: graphObject['alert'] = self.alert return graphObject + +@attr.s +class Svg(object): + """Generates SVG panel json structure + + Grafana doc on SVG: https://grafana.com/grafana/plugins/marcuscalidus-svg-panel + + :param dataSource: Grafana datasource name + :param targets: list of metric requests for chosen datasource + :param title: panel title + :param description: optional panel description + :param editable: defines if panel is editable via web interfaces + :param format: defines value units + :param jsCodeFilePath: path to javascript file to be run on dashboard refresh + :param jsCodeInitFilePath: path to javascript file to be run after the first initialization of the SVG + :param height: defines panel height + :param id: panel id + :param interval: defines time interval between metric queries + :param links: additional web links + :param reduceCalc: algorithm for reduction to a single value: keys 'mean' 'lastNotNull' 'last' 'first' 'firstNotNull' 'min' 'max' 'sum' 'total' + :param span: defines the number of spans that will be used for panel + :param svgFilePath: path to SVG image file to be displayed + """ + + dataSource = attr.ib() + targets = attr.ib() + title = attr.ib() + description = attr.ib(default=None) + editable = attr.ib(default=True, validator=instance_of(bool)) + format = attr.ib(default="none") + jsCodeFilePath = attr.ib(default="", validator=instance_of(str)) + jsCodeInitFilePath = attr.ib(default="", validator=instance_of(str)) + height = attr.ib(default=None) + id = attr.ib(default=None) + links = attr.ib(default=attr.Factory(list)) + span = attr.ib(default=6) + svgFilePath = attr.ib(default="", validator=instance_of(str)) + + @staticmethod + def read_file(file_path): + if file_path: + with open(file_path) as f: + read_data = f.read() + return read_data + else: + return "" + + def to_json_data(self): + + js_code = self.read_file(self.jsCodeFilePath) + js_init_code = self.read_file(self.jsCodeInitFilePath) + svg_data = self.read_file(self.svgFilePath) + + return { + 'datasource': self.dataSource, + 'description': self.description, + 'editable': self.editable, + 'id': self.id, + 'links': self.links, + 'height': self.height, + "format": self.format, + 'js_code': js_code, + 'js_init_code': js_init_code, + 'span': self.span, + 'svg_data': svg_data, + 'targets': self.targets, + 'title': self.title, + 'type': SVG_TYPE, + 'useSVGBuilder': False + } From cc764c3717af8c22bcf225a380f44754b531400f Mon Sep 17 00:00:00 2001 From: James Gibson Date: Wed, 1 Jul 2020 10:56:39 +0100 Subject: [PATCH 127/403] Fix linting errors --- CHANGELOG.rst | 2 +- grafanalib/core.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0970e73c..4e761f9a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,7 +12,7 @@ Changes * Add Elasticsearch bucket script pipeline aggregator * Added ability to hide metrics for Elasticsearch MetricAggs * Add derivative metric aggregation for Elasticsearch -* Add ``SVG`` class to support the SVG panel +* Add ``Svg`` class to support the SVG panel * ... diff --git a/grafanalib/core.py b/grafanalib/core.py index 17e2ef8e..a2b941d7 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2210,6 +2210,7 @@ def to_json_data(self): graphObject['alert'] = self.alert return graphObject + @attr.s class Svg(object): """Generates SVG panel json structure @@ -2269,7 +2270,7 @@ def to_json_data(self): 'id': self.id, 'links': self.links, 'height': self.height, - "format": self.format, + 'format': self.format, 'js_code': js_code, 'js_init_code': js_init_code, 'span': self.span, From 130d59426ab69431cba7ddfc54540faff2354106 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Wed, 1 Jul 2020 12:36:31 +0100 Subject: [PATCH 128/403] Add support for the Stat panel, which replaces SingleStat The SingleStat panel was deprecated in Grafana 7.0, Stat is the the replacement for this. The Stat panel uses a new mappings format so the classes StatMapping, StatValueMapping and StatRangeMapping were added to support this. --- CHANGELOG.rst | 1 + grafanalib/core.py | 166 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 473469c6..65e9e0c3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ Changes * Add Elasticsearch bucket script pipeline aggregator * Added ability to hide metrics for Elasticsearch MetricAggs * Add derivative metric aggregation for Elasticsearch +* Add ``Stat`` class (and ``StatMapping``, ``StatValueMapping``, ``StatRangeMapping``) to support the Stat panel * ... diff --git a/grafanalib/core.py b/grafanalib/core.py index d9fc924a..18a20d0d 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -71,6 +71,7 @@ def to_json_data(self): ABSOLUTE_TYPE = 'absolute' DASHBOARD_TYPE = 'dashboard' GRAPH_TYPE = 'graph' +STAT_TYPE = 'stat' SINGLESTAT_TYPE = 'singlestat' TABLE_TYPE = 'table' TEXT_TYPE = 'text' @@ -1239,10 +1240,175 @@ def to_json_data(self): } +@attr.s +class Stat(object): + """Generates Stat panel json structure + + Grafana doc on stat: https://grafana.com/docs/grafana/latest/panels/visualizations/stat-panel/ + + :param dataSource: Grafana datasource name + :param targets: list of metric requests for chosen datasource + :param title: panel title + :param colorMode: defines if Grafana will color panel background: keys "value" "background" + :param graphMode: defines if Grafana will draw graph: keys 'area' 'none' + :param orientation: Stacking direction in case of multiple series or fields: keys 'auto' 'horizontal' 'vertical' + :param alignment: defines value & title positioning: keys 'auto' 'centre' + :param description: optional panel description + :param editable: defines if panel is editable via web interfaces + :param format: defines value units + :param height: defines panel height + :param id: panel id + :param decimals: number of decimals to display + :param interval: defines time interval between metric queries + :param links: additional web links + :param mappings: the list of values to text mappings + This should be a list of StatMapping objects + https://grafana.com/docs/grafana/latest/panels/field-configuration-options/#value-mapping + :param reduceCalc: algorithm for reduction to a single value: keys + 'mean' 'lastNotNull' 'last' 'first' 'firstNotNull' 'min' 'max' 'sum' 'total' + :param span: defines the number of spans that will be used for panel + :param thresholds: single stat thresholds + """ + + dataSource = attr.ib() + targets = attr.ib() + title = attr.ib() + description = attr.ib(default=None) + colorMode = attr.ib(default='value') + graphMode = attr.ib(default='area') + orientation = attr.ib(default='auto') + alignment = attr.ib(default='auto') + editable = attr.ib(default=True, validator=instance_of(bool)) + format = attr.ib(default='none') + height = attr.ib(default=None) + id = attr.ib(default=None) + links = attr.ib(default=attr.Factory(list)) + mappings = attr.ib(default=attr.Factory(list)) + span = attr.ib(default=6) + thresholds = attr.ib(default='') + timeFrom = attr.ib(default=None) + reduceCalc = attr.ib(default='mean', type=str) + decimals = attr.ib(default=None) + + def to_json_data(self): + return { + 'datasource': self.dataSource, + 'description': self.description, + 'editable': self.editable, + 'id': self.id, + 'links': self.links, + 'height': self.height, + 'fieldConfig': { + 'defaults': { + 'custom': {}, + 'decimals': self.decimals, + 'mappings': self.mappings, + 'thresholds': { + 'mode': 'absolute', + 'steps': self.thresholds, + }, + 'unit': self.format + } + }, + 'options': { + 'colorMode': self.colorMode, + 'graphMode': self.graphMode, + 'justifyMode': self.alignment, + 'orientation': self.orientation, + 'reduceOptions': { + 'calcs': [ + self.reduceCalc + ], + 'fields': '', + 'values': False + } + }, + 'span': self.span, + 'targets': self.targets, + 'title': self.title, + 'type': STAT_TYPE, + 'timeFrom': self.timeFrom, + } + + +@attr.s +class StatMapping(object): + """ + Generates json structure for the value mapping for the Stat panel: + :param text: Sting that will replace input value + :param value: Value to be replaced + :param startValue: When using a range, the start value of the range + :param endValue: When using a range, the end value of the range + :param id: panel id + """ + + text = attr.ib() + mapValue = attr.ib(default="", validator=instance_of(str)) + startValue = attr.ib(default="", validator=instance_of(str)) + endValue = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=None) + + def to_json_data(self): + mappingType = MAPPING_TYPE_VALUE_TO_TEXT if self.mapValue else MAPPING_TYPE_RANGE_TO_TEXT + + return { + 'operator': "", + 'text': self.text, + 'type': mappingType, + 'value': self.mapValue, + 'from': self.startValue, + 'to': self.endValue, + 'id': self.id + } + + +@attr.s +class StatValueMapping(object): + """ + Generates json structure for the value mappings for the StatPanel: + :param text: Sting that will replace input value + :param value: Value to be replaced + :param id: panel id + """ + + text = attr.ib() + mapValue = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=None) + + def to_json_data(self): + return StatMapping(self.text, mapValue=self.mapValue, id=self.id) + + +@attr.s +class StatRangeMapping(object): + """ + Generates json structure for the value mappings for the StatPanel: + :param text: Sting that will replace input value + :param startValue: When using a range, the start value of the range + :param endValue: When using a range, the end value of the range + :param id: panel id + """ + + text = attr.ib() + startValue = attr.ib(default="", validator=instance_of(str)) + endValue = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=None) + + def to_json_data(self): + return StatMapping( + self.text, + startValue=self.startValue, + endValue=self.endValue, + id=self.id + ) + + @attr.s class SingleStat(object): """Generates Single Stat panel json structure + This panel was deprecated in Grafana 7.0, please use Stat instead + Grafana doc on singlestat: https://grafana.com/docs/grafana/latest/features/panels/singlestat/ :param dataSource: Grafana datasource name From bfd624bb99add6146a0ec2bbe692bdfcec7017c8 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Mon, 10 Aug 2020 08:52:01 +0100 Subject: [PATCH 129/403] Fix PieChart merge conflict --- CHANGELOG.rst | 3 +- grafanalib/core.py | 69 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f50e0ebc..ccb39634 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,11 +14,12 @@ Changes * Add derivative metric aggregation for Elasticsearch * Add ``Stat`` class (and ``StatMapping``, ``StatValueMapping``, ``StatRangeMapping``) to support the Stat panel * Add ``Svg`` class to support the SVG panel +* Add ``PieChart`` class for creating Pie Chart panels * ... 0.5.7 (2020-05-11) -=========== +================== Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index a284e0e5..b04a48d4 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -81,6 +81,7 @@ def to_json_data(self): HEATMAP_TYPE = "heatmap" STATUSMAP_TYPE = "flant-statusmap-panel" SVG_TYPE = 'marcuscalidus-svg-panel' +PIE_CHART_TYPE = 'grafana-piechart-panel' DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -2405,9 +2406,7 @@ def to_json_data(self): @attr.s class Svg(object): """Generates SVG panel json structure - Grafana doc on SVG: https://grafana.com/grafana/plugins/marcuscalidus-svg-panel - :param dataSource: Grafana datasource name :param targets: list of metric requests for chosen datasource :param title: panel title @@ -2471,3 +2470,69 @@ def to_json_data(self): 'type': SVG_TYPE, 'useSVGBuilder': False } + + +@attr.s +class PieChart(object): + """Generates Pie Chart panel json structure + Grafana doc on Pie Chart: https://grafana.com/grafana/plugins/grafana-piechart-panel + :param dataSource: Grafana datasource name + :param targets: list of metric requests for chosen datasource + :param title: panel title + :param description: optional panel description + :param editable: defines if panel is editable via web interfaces + :param format: defines value units + :param height: defines panel height + :param id: panel id + :param pieType: defines the shape of the pie chart (pie or donut) + :param showLegend: defines if the legend should be shown + :param showLegend: defines if the legend should show values + :param legendType: defines where the legend position + :param links: additional web links + :param span: defines the number of spans that will be used for panel + """ + + dataSource = attr.ib() + targets = attr.ib() + title = attr.ib() + description = attr.ib(default=None) + editable = attr.ib(default=True, validator=instance_of(bool)) + format = attr.ib(default='none') + height = attr.ib(default=None) + id = attr.ib(default=None) + links = attr.ib(default=attr.Factory(list)) + legendType = attr.ib(default='Right side') + pieType = attr.ib(default='pie') + showLegend = attr.ib(default=True) + showLegendValues = attr.ib(default=True) + span = attr.ib(default=6) + thresholds = attr.ib(default='') + timeFrom = attr.ib(default=None) + + def to_json_data(self): + return { + 'datasource': self.dataSource, + 'description': self.description, + 'editable': self.editable, + 'format': self.format, + 'id': self.id, + 'links': self.links, + 'pieType': self.pieType, + 'height': self.height, + 'fieldConfig': { + 'defaults': { + 'custom': {}, + }, + 'overrides': [] + }, + 'legend': { + 'show': self.showLegend, + 'values': self.showLegendValues + }, + 'legendType': self.legendType, + 'span': self.span, + 'targets': self.targets, + 'title': self.title, + 'type': PIE_CHART_TYPE, + 'timeFrom': self.timeFrom, + } From 5b6d74686a9ab718492e6c41f1288cb4c5fbd7fd Mon Sep 17 00:00:00 2001 From: James Gibson Date: Mon, 24 Aug 2020 15:45:59 +0100 Subject: [PATCH 130/403] Add James Gibson as maintainer --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 3531d239..5bb78674 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,2 +1,3 @@ Bryan Boreham (@bboreham) Matt Richter (@matthewmrichter) +James Gibson From ec7449b9230e0250592a9ba219e9f7edcc1550b6 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Mon, 24 Aug 2020 15:55:43 +0100 Subject: [PATCH 131/403] Update community meeting link to Google --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index de4cecba..ac432f77 100644 --- a/README.rst +++ b/README.rst @@ -83,7 +83,7 @@ We'd like you to join the ``grafanalib`` community! Talk to us on Slack (see the or join us for one of our next meetings): - Meetings take place monthly: third Friday of the month 15:00 UTC -- https://zoom.us/j/405935052 +- https://meet.google.com/cys-jyto-rju - `Meeting minutes and agenda `_ (includes links to meeting recordings) From b20f4c6b287b74b38fb005ec7820098c562f6833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Julia=CC=81n?= Date: Mon, 28 Sep 2020 12:11:12 +0200 Subject: [PATCH 132/403] Add transparency setting to panels that missed it --- grafanalib/core.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index b04a48d4..8df95fe9 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1289,6 +1289,7 @@ class Stat(object): 'mean' 'lastNotNull' 'last' 'first' 'firstNotNull' 'min' 'max' 'sum' 'total' :param span: defines the number of spans that will be used for panel :param thresholds: single stat thresholds + :param transparent: defines if the panel should be transparent """ dataSource = attr.ib() @@ -1308,6 +1309,7 @@ class Stat(object): span = attr.ib(default=6) thresholds = attr.ib(default='') timeFrom = attr.ib(default=None) + transparent = attr.ib(default=False, validator=instance_of(bool)) reduceCalc = attr.ib(default='mean', type=str) decimals = attr.ib(default=None) @@ -1347,6 +1349,7 @@ def to_json_data(self): 'span': self.span, 'targets': self.targets, 'title': self.title, + 'transparent': self.transparent, 'type': STAT_TYPE, 'timeFrom': self.timeFrom, } @@ -2185,6 +2188,7 @@ class Heatmap(object): :param yBucketNumber :param highlightCards: boolean :param hideZeroBuckets: boolean + :param transparent: defines if the panel should be transparent """ title = attr.ib() @@ -2218,6 +2222,7 @@ class Heatmap(object): hideZeroBuckets = attr.ib(default=False) highlightCards = attr.ib(default=True) options = attr.ib(default=None) + transparent = attr.ib(default=False, validator=instance_of(bool)) xAxis = attr.ib( default=attr.Factory(XAxis), @@ -2254,6 +2259,7 @@ def to_json_data(self): 'targets': self.targets, 'title': self.title, 'tooltip': self.tooltip, + 'transparent': self.transparent, 'type': HEATMAP_TYPE, 'xAxis': self.xAxis, 'xBucketNumber': self.xBucketNumber, @@ -2490,6 +2496,7 @@ class PieChart(object): :param legendType: defines where the legend position :param links: additional web links :param span: defines the number of spans that will be used for panel + :param transparent: defines if the panel is transparent """ dataSource = attr.ib() @@ -2508,6 +2515,7 @@ class PieChart(object): span = attr.ib(default=6) thresholds = attr.ib(default='') timeFrom = attr.ib(default=None) + transparent = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): return { @@ -2535,4 +2543,5 @@ def to_json_data(self): 'title': self.title, 'type': PIE_CHART_TYPE, 'timeFrom': self.timeFrom, + 'transparent': self.transparent } From 457cd4fc28089f92680fec682bb1cb28bf33fee5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guillermo=20Julia=CC=81n?= Date: Mon, 28 Sep 2020 12:14:37 +0200 Subject: [PATCH 133/403] Update changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ccb39634..87401ce4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,7 @@ Changes * Add ``Stat`` class (and ``StatMapping``, ``StatValueMapping``, ``StatRangeMapping``) to support the Stat panel * Add ``Svg`` class to support the SVG panel * Add ``PieChart`` class for creating Pie Chart panels +* Add `transparent` setting to classes that were missing it (Heatmap, PieChart) * ... From 9627c5c6107e2efaee945e592bc45b3c40a656f7 Mon Sep 17 00:00:00 2001 From: Matt Richter Date: Fri, 9 Oct 2020 10:39:58 -0400 Subject: [PATCH 134/403] A few blank lines as requested by the linter. --- grafanalib/core.py | 1 + grafanalib/influxdb.py | 1 + 2 files changed, 2 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index 24e8b3bc..8df95fe9 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -357,6 +357,7 @@ def to_json_data(self): 'datasource': self.datasource, } + @attr.s class Tooltip(object): diff --git a/grafanalib/influxdb.py b/grafanalib/influxdb.py index cd965d8e..fc59a561 100644 --- a/grafanalib/influxdb.py +++ b/grafanalib/influxdb.py @@ -4,6 +4,7 @@ TIME_SERIES_TARGET_FORMAT = "time_series" + @attr.s class InfluxDBTarget(object): """ From 9717359d73aba697000646c4e48a50bdc58505a7 Mon Sep 17 00:00:00 2001 From: David Worth Date: Wed, 24 Oct 2018 14:47:40 -0600 Subject: [PATCH 135/403] add auto_ref_ids --- CHANGELOG.rst | 1 + grafanalib/core.py | 31 +++++++++++++++ grafanalib/tests/test_grafanalib.py | 58 +++++++++++++++++++++++++++++ 3 files changed, 90 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2cd11a31..147ca41e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,7 @@ TBA * Add ``PieChart`` class for creating Pie Chart panels * Add `transparent` setting to classes that were missing it (Heatmap, PieChart) * Add InfluxDB data source +* Add ``auto_ref_ids`` to ``Graph``s * ... diff --git a/grafanalib/core.py b/grafanalib/core.py index 8df95fe9..271475ff 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -10,6 +10,7 @@ import itertools import math from numbers import Number +import string import warnings @@ -1127,6 +1128,36 @@ def to_json_data(self): graphObject['alert'] = self.alert return graphObject + def _iter_targets(self): + for target in self.targets: + yield target + + def _map_targets(self, f): + return attr.assoc(self, targets=[f(t) for t in self.targets]) + + def auto_ref_ids(self): + """Give unique IDs all the panels without IDs. + + Returns a new ``Graph`` that is the same as this one, except all of + the metrics have their ``refId`` property set. Any panels which had + an ``refId`` property set will keep that property, all others will + have auto-generated IDs provided for them. + """ + ref_ids = set([t.refId for t in self._iter_targets() if t.refId]) + double_candidate_refs = \ + [p[0] + p[1] for p + in itertools.product(string.ascii_uppercase, repeat=2)] + candidate_ref_ids = itertools.chain( + string.ascii_uppercase, + double_candidate_refs, + ) + + auto_ref_ids = (i for i in candidate_ref_ids if i not in ref_ids) + + def set_refid(t): + return t if t.refId else attr.assoc(t, refId=next(auto_ref_ids)) + return self._map_targets(set_refid) + @attr.s class SparkLine(object): diff --git a/grafanalib/tests/test_grafanalib.py b/grafanalib/tests/test_grafanalib.py index 9192a83b..a5a67046 100644 --- a/grafanalib/tests/test_grafanalib.py +++ b/grafanalib/tests/test_grafanalib.py @@ -62,6 +62,64 @@ def test_auto_id(): assert dashboard.rows[0].panels[0].id == 1 +def test_auto_refids_preserves_provided_ids(): + """ + auto_ref_ids() provides refIds for all targets without refIds already + set. + """ + dashboard = G.Dashboard( + title="Test dashboard", + rows=[ + G.Row(panels=[ + G.Graph( + title="CPU Usage by Namespace (rate[5m])", + targets=[ + G.Target( + expr='whatever #Q', + legendFormat='{{namespace}}', + ), + G.Target( + expr='hidden whatever', + legendFormat='{{namespace}}', + refId='Q', + ), + G.Target( + expr='another target' + ), + ], + ).auto_ref_ids() + ]), + ], + ) + assert dashboard.rows[0].panels[0].targets[0].refId == 'A' + assert dashboard.rows[0].panels[0].targets[1].refId == 'Q' + assert dashboard.rows[0].panels[0].targets[2].refId == 'B' + + +def test_auto_refids(): + """ + auto_ref_ids() provides refIds for all targets without refIds already + set. + """ + dashboard = G.Dashboard( + title="Test dashboard", + rows=[ + G.Row(panels=[ + G.Graph( + title="CPU Usage by Namespace (rate[5m])", + targets=[G.Target(expr="metric %d" % i) + for i in range(53)], + ).auto_ref_ids() + ]), + ], + ) + assert dashboard.rows[0].panels[0].targets[0].refId == 'A' + assert dashboard.rows[0].panels[0].targets[25].refId == 'Z' + assert dashboard.rows[0].panels[0].targets[26].refId == 'AA' + assert dashboard.rows[0].panels[0].targets[51].refId == 'AZ' + assert dashboard.rows[0].panels[0].targets[52].refId == 'BA' + + def test_row_show_title(): row = G.Row().to_json_data() assert row['title'] == 'New row' From 9d9cc4f8af9d728a4a62fe3729f28ab59ab9a48b Mon Sep 17 00:00:00 2001 From: Lars de Ridder Date: Sat, 4 Jan 2020 07:24:09 +0100 Subject: [PATCH 136/403] Added more YAxis formats, added Threshold and SeriesOverride types --- grafanalib/core.py | 95 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 25 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 271475ff..2c724ec3 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -5,13 +5,15 @@ arbitrary Grafana JSON. """ -import attr -from attr.validators import instance_of, in_ import itertools import math -from numbers import Number + import string import warnings +from numbers import Number + +import attr +from attr.validators import in_, instance_of @attr.s @@ -115,6 +117,26 @@ def to_json_data(self): BYTES_FORMAT = "bytes" BITS_PER_SEC_FORMAT = "bps" BYTES_PER_SEC_FORMAT = "Bps" +NONE_FORMAT = "none" +JOULE_FORMAT = "joule" +WATTHOUR_FORMAT = "watth" +WATT_FORMAT = "watt" +KWATT_FORMAT = "kwatt" +KWATTHOUR_FORMAT = "kwatth" +VOLT_FORMAT = "volt" +BAR_FORMAT = "pressurebar" +PSI_FORMAT = "pressurepsi" +CELSIUS_FORMAT = "celsius" +KELVIN_FORMAT = "kelvin" +GRAM_FORMAT = "massg" +EUR_FORMAT = "currencyEUR" +USD_FORMAT = "currencyUSD" +METER_FORMAT = "lengthm" +SQUARE_METER_FORMAT = "areaM2" +CUBIC_METER_FORMAT = "m3" +LITRE_FORMAT = "litre" +PERCENT_FORMAT = "percent" +VOLT_AMPERE_FORMAT = "voltamp" # Alert rule state STATE_NO_DATA = "no_data" @@ -1884,27 +1906,6 @@ def to_json_data(self): } -@attr.s -class Threshold(object): - """Threshold for a gauge - - :param color: color of threshold - :param index: index of color in gauge - :param value: when to use this color will be null if index is 0 - """ - - color = attr.ib() - index = attr.ib(validator=instance_of(int)) - value = attr.ib(validator=instance_of(float)) - - def to_json_data(self): - return { - "color": self.color, - "index": self.index, - "value": "null" if self.index == 0 else self.value, - } - - @attr.s class BarGauge(object): """Generates Bar Gauge panel json structure @@ -2068,7 +2069,7 @@ class GaugePanel(object): :param links: additional web links :param max: maximum value of the gauge :param maxDataPoints: maximum metric query results, - that will be used for rendering + that will be used for rendering :param min: minimum value of the gauge :param minSpan: minimum span number :param rangeMaps: the list of value to text mappings @@ -2576,3 +2577,47 @@ def to_json_data(self): 'timeFrom': self.timeFrom, 'transparent': self.transparent } + + +@attr.s +class Threshold(object): + """Threshold for a gauge + + :param color: color of threshold + :param index: index of color in gauge + :param value: when to use this color will be null if index is 0 + """ + + color = attr.ib() + index = attr.ib(validator=instance_of(int)) + line = attr.ib(default=True, validator=instance_of(bool)) + op = attr.ib(default="gt") + value = attr.ib(validator=instance_of(float)) + yaxis = attr.ib(default="left") + + def to_json_data(self): + return { + "op": self.op, + "yaxis": self.yaxis, + "color": self.color, + "line": self.line, + "index": self.index, + "value": "null" if self.index == 0 else self.value, + } + + +class SeriesOverride(object): + alias = attr.ib() + bars = attr.ib(default=False) + lines = attr.ib(default=True) + yaxis = attr.ib(default=1) + color = attr.ib(default=None) + + def to_json_data(self): + return { + "alias": self.alias, + "bars": self.bars, + "lines": self.lines, + "yaxis": self.yaxis, + "color": self.color, + } From 9151ee0ae602c1697b2bb24b6475cc3227fdee05 Mon Sep 17 00:00:00 2001 From: Lars de Ridder Date: Sat, 4 Jan 2020 07:27:17 +0100 Subject: [PATCH 137/403] Added to changelog --- CHANGELOG.rst | 2 ++ grafanalib/core.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 147ca41e..c6e35880 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -103,6 +103,8 @@ Changes * Add ``STATE_OK`` for alerts * Add named values for the Template.hide parameter * Add cardinality metric aggregator for ElasticSearch +* Add Threshold and Series Override types +* Add more YAxis formats Many thanks to contributors @kevingessner, @2easy, @vicmarbev, @butlerx. diff --git a/grafanalib/core.py b/grafanalib/core.py index 2c724ec3..c77f1ae5 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2589,10 +2589,10 @@ class Threshold(object): """ color = attr.ib() + value = attr.ib(validator=instance_of(float)) index = attr.ib(validator=instance_of(int)) line = attr.ib(default=True, validator=instance_of(bool)) op = attr.ib(default="gt") - value = attr.ib(validator=instance_of(float)) yaxis = attr.ib(default="left") def to_json_data(self): From 6b94ea1c06f14f9d4795ecd95a5d5a760203ebe8 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 2 Nov 2020 11:07:56 +0100 Subject: [PATCH 138/403] add missing bits, sumarise release, thank James --- CHANGELOG.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c6e35880..d847a189 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,15 +2,18 @@ Changelog ========= +0.5.8 (2020-11-02) +================== + +This release adds quite a few new classes to grafanalib, ElasticSearch support was improved and support for InfluxDB data sources was added. -0.5.8 (TBD) -=========== +We would also very much like to welcome James Gibson as new maintainer of grafanalib. Thanks a lot for stepping up to this role! Changes ------- -TBA -======= +* Added more YAxis formats, added Threshold and SeriesOverride types +* dataLinks support in graphs * Add Elasticsearch bucket script pipeline aggregator * Added ability to hide metrics for Elasticsearch MetricAggs * Add derivative metric aggregation for Elasticsearch @@ -20,7 +23,8 @@ TBA * Add `transparent` setting to classes that were missing it (Heatmap, PieChart) * Add InfluxDB data source * Add ``auto_ref_ids`` to ``Graph``s -* ... + +Thanks a lot for your contributions to this release, @DWalker487, @JamesGibo, @daveworth, @dholbach, @fauust, @larsderidder, @matthewmrichter. 0.5.7 (2020-05-11) From 5f0f6d844812891373e30bc3d45cb97489b57a9e Mon Sep 17 00:00:00 2001 From: James Gibson Date: Mon, 2 Nov 2020 10:14:24 +0000 Subject: [PATCH 139/403] bug fix: threshold parameter order flipped --- grafanalib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index c77f1ae5..a42db77f 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2589,8 +2589,8 @@ class Threshold(object): """ color = attr.ib() - value = attr.ib(validator=instance_of(float)) index = attr.ib(validator=instance_of(int)) + value = attr.ib(validator=instance_of(float)) line = attr.ib(default=True, validator=instance_of(bool)) op = attr.ib(default="gt") yaxis = attr.ib(default="left") From 322118bc3b916946b0f4594a723424fa3d0f9e8d Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 2 Nov 2020 11:17:27 +0100 Subject: [PATCH 140/403] bump version in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c658d9f8..ccb29df8 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.7', + version='0.5.8', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 05931bd7ddf969afc1c8d59163bb1e2d96ca438c Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 2 Nov 2020 11:21:45 +0100 Subject: [PATCH 141/403] the file we point to is called 'example.dashboard.py' --- docs/getting-started.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 759af6d2..1c0a3131 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -27,12 +27,12 @@ for inspiration. Generating dashboards ===================== -If you save the above as ``frontend.dashboard.py`` (the suffix must be +If you save the above as ``example.dashboard.py`` (the suffix must be ``.dashboard.py``), you can then generate the JSON dashboard with: .. code-block:: console - $ generate-dashboard -o frontend.json frontend.dashboard.py + $ generate-dashboard -o frontend.json example.dashboard.py Installation ============ From a75a08cd594ac728fd18732929ccac1a74be25c3 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 2 Nov 2020 11:30:10 +0100 Subject: [PATCH 142/403] add dependabot config --- .github/dependabot.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..21a1b465 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,16 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "pip" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" + + - package-ecosystem: "github-action" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" From 76f56e113e3069c9d93a8414804d4df01234b01a Mon Sep 17 00:00:00 2001 From: James Gibson Date: Mon, 2 Nov 2020 10:41:26 +0000 Subject: [PATCH 143/403] Fix github actions typo --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 21a1b465..ae1d01ab 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,7 +10,7 @@ updates: schedule: interval: "daily" - - package-ecosystem: "github-action" # See documentation for possible values + - package-ecosystem: "github-actions" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" From 5e8da0ad990f77fa70d8fc8a0aa6426335cb715a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 10:42:00 +0000 Subject: [PATCH 144/403] Bump actions/setup-python from v1 to v2.1.4 Bumps [actions/setup-python](https://github.com/actions/setup-python) from v1 to v2.1.4. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v1...41b7212b1668f5de9d65e9c82aa777e6bbedb3a8) Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 59d5491d..ba0a0e0d 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v1 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2.1.4 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index ccda373e..4ffa5816 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@master - name: Set up Python 3.7 - uses: actions/setup-python@v1 + uses: actions/setup-python@v2.1.4 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6e093115..8d7dcc67 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@master - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v2.1.4 with: python-version: ${{ matrix.python }} - name: Run tests From ea24a3d7be2b322a3e73e941b9723cef34a16aee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 10:42:00 +0000 Subject: [PATCH 145/403] Update actions/checkout requirement to v2.3.3 Updates the requirements on [actions/checkout](https://github.com/actions/checkout) to permit the latest version. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/commits/a81bbbf8298c0fa03ea29cdc473d45769f953675) Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 59d5491d..1e036fbc 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -7,7 +7,7 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2.3.3 - name: Set up Python uses: actions/setup-python@v1 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index ccda373e..104c112d 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -7,7 +7,7 @@ jobs: name: Build and publish Python 🐍 distributions 📦 to PyPI runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@master + - uses: actions/checkout@v2.3.3 - name: Set up Python 3.7 uses: actions/setup-python@v1 with: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6e093115..7e769aac 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: matrix: python: [3.5, 3.7, 3.8] steps: - - uses: actions/checkout@master + - uses: actions/checkout@v2.3.3 - name: Set up Python uses: actions/setup-python@v1 with: From b7d1a39844a46e62ea95671b90f5fcc74381c103 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 10:42:05 +0000 Subject: [PATCH 146/403] Bump sphinx-rtd-theme from 0.4.3 to 0.5.0 Bumps [sphinx-rtd-theme](https://github.com/rtfd/sphinx_rtd_theme) from 0.4.3 to 0.5.0. - [Release notes](https://github.com/rtfd/sphinx_rtd_theme/releases) - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/rtfd/sphinx_rtd_theme/compare/0.4.3...0.5.0) Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 41a40666..24a3e1a3 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx == 3.0.3 -sphinx_rtd_theme == 0.4.3 +sphinx_rtd_theme == 0.5.0 From dbb1781af6e9f62b0e8315a9d5c8699d5663d771 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 2 Nov 2020 11:51:01 +0100 Subject: [PATCH 147/403] fix warnings to restore sphinx docs build --- CHANGELOG.rst | 2 +- grafanalib/elasticsearch.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d847a189..b9453058 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -22,7 +22,7 @@ Changes * Add ``PieChart`` class for creating Pie Chart panels * Add `transparent` setting to classes that were missing it (Heatmap, PieChart) * Add InfluxDB data source -* Add ``auto_ref_ids`` to ``Graph``s +* Add ``auto_ref_ids`` to ``Graph`` Thanks a lot for your contributions to this release, @DWalker487, @JamesGibo, @daveworth, @dholbach, @fauust, @larsderidder, @matthewmrichter. diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index b9275af2..d1d08218 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -201,7 +201,7 @@ class BucketScriptAgg(object): :param fields: dictionary of field names mapped to aggregation IDs to be used in the bucket script e.g. { "field1":1 }, which allows the output of aggregate ID 1 to be referenced as - params.field1 in the bucket script + params.field1 in the bucket script :param script: script to apply to the data using the variables specified in 'fields' :param id: id of the aggregator :param hide: show/hide the metric in the final panel display From 761bd0604044215de2d447016312d5f3d07faa16 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 2 Nov 2020 12:30:52 +0100 Subject: [PATCH 148/403] update meeting information --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index ac432f77..1f154e6c 100644 --- a/README.rst +++ b/README.rst @@ -82,8 +82,8 @@ Community We'd like you to join the ``grafanalib`` community! Talk to us on Slack (see the links), or join us for one of our next meetings): -- Meetings take place monthly: third Friday of the month 15:00 UTC -- https://meet.google.com/cys-jyto-rju +- Meetings take place monthly: fourth Friday of the month 15:00 UTC +- https://weaveworks.zoom.us/j/96824669060 - `Meeting minutes and agenda `_ (includes links to meeting recordings) From 1972d499a1474b9b2e7b36816b3fcb4ed04b6bfb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Nov 2020 11:52:20 +0000 Subject: [PATCH 149/403] Bump sphinx from 3.0.3 to 3.2.1 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.0.3 to 3.2.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.0.3...v3.2.1) Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 24a3e1a3..a856123c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.0.3 +sphinx == 3.2.1 sphinx_rtd_theme == 0.5.0 From e4d2e9b3bc7792e0d4a9038002b18ece4a682315 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 2 Nov 2020 13:27:19 +0100 Subject: [PATCH 150/403] update supported versions of python3 --- .github/workflows/run-tests.yml | 2 +- README.rst | 2 +- docs/getting-started.rst | 2 +- setup.py | 3 ++- tox.ini | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index ccced684..8d07020e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-18.04 strategy: matrix: - python: [3.5, 3.7, 3.8] + python: [3.6, 3.7, 3.8, 3.9] steps: - uses: actions/checkout@v2.3.3 - name: Set up Python diff --git a/README.rst b/README.rst index 1f154e6c..51ef0b9b 100644 --- a/README.rst +++ b/README.rst @@ -57,7 +57,7 @@ Support This library is in its very early stages. We'll probably make changes that break backwards compatibility, although we'll try hard not to. -grafanalib works with Python 3.4 through 3.8. +grafanalib works with Python 3.6 through 3.9. Developing ========== diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 1c0a3131..90fd0bbf 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -49,7 +49,7 @@ Support This library is in its very early stages. We'll probably make changes that break backwards compatibility, although we'll try hard not to. -grafanalib works with Python 3.4, 3.5, 3.6 and 3.7. +grafanalib works with Python 3.6, 3.7, 3.8 and 3.9. Developing ========== diff --git a/setup.py b/setup.py index ccb29df8..71e84452 100644 --- a/setup.py +++ b/setup.py @@ -33,9 +33,10 @@ def local_file(name): 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', 'Topic :: System :: Monitoring', ], install_requires=[ diff --git a/tox.ini b/tox.ini index 4e719340..8f5b6277 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py35, py37, py38 +envlist = py36, py37, py38, py39 [testenv] commands = pytest -o junit_family=xunit2 --junitxml=test-results/junit-{envname}.xml From 85d550ba701d4369ac5975d9f11987c5c7453c8a Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 2 Nov 2020 13:30:19 +0100 Subject: [PATCH 151/403] add changelog entry --- CHANGELOG.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b9453058..80391186 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,14 @@ Changelog ========= +0.5.9 (TBD) +=========== + +Changes +------- + +* Change supported python versions from 3.6 to 3.9 + 0.5.8 (2020-11-02) ================== From 0c25bc8f3b3bda281779cf74dcb4206e0aef3cd4 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Mon, 2 Nov 2020 15:51:54 +0000 Subject: [PATCH 152/403] Make quote coding style consistent --- grafanalib/_gen.py | 4 +- grafanalib/core.py | 654 ++++++++++++++++++------------------ grafanalib/elasticsearch.py | 26 +- grafanalib/influxdb.py | 2 +- grafanalib/opentsdb.py | 62 ++-- grafanalib/weave.py | 24 +- grafanalib/zabbix.py | 528 ++++++++++++++--------------- 7 files changed, 650 insertions(+), 650 deletions(-) diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index 996efa29..857d94ac 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -22,12 +22,12 @@ def load_dashboard(path): """ if sys.version_info[0] == 3 and sys.version_info[1] >= 5: import importlib.util - spec = importlib.util.spec_from_file_location("dashboard", path) + spec = importlib.util.spec_from_file_location('dashboard', path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) else: import importlib - module = importlib.load_source("dashboard", path) + module = importlib.load_source('dashboard', path) marker = object() dashboard = getattr(module, 'dashboard', marker) if dashboard is marker: diff --git a/grafanalib/core.py b/grafanalib/core.py index a42db77f..6716d4ce 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -42,7 +42,7 @@ class Pixels(object): num = attr.ib(validator=instance_of(int)) def to_json_data(self): - return '{}px'.format(self.num) + return "{}px".format(self.num) @attr.s @@ -50,7 +50,7 @@ class Percent(object): num = attr.ib(default=100, validator=instance_of(Number)) def to_json_data(self): - return '{}%'.format(self.num) + return "{}%".format(self.num) GREY1 = RGBA(216, 200, 27, 0.27) @@ -78,11 +78,11 @@ def to_json_data(self): SINGLESTAT_TYPE = 'singlestat' TABLE_TYPE = 'table' TEXT_TYPE = 'text' -ALERTLIST_TYPE = "alertlist" -BARGAUGE_TYPE = "bargauge" -GAUGE_TYPE = "gauge" -HEATMAP_TYPE = "heatmap" -STATUSMAP_TYPE = "flant-statusmap-panel" +ALERTLIST_TYPE = 'alertlist' +BARGAUGE_TYPE = 'bargauge' +GAUGE_TYPE = 'gauge' +HEATMAP_TYPE = 'heatmap' +STATUSMAP_TYPE = 'flant-statusmap-panel' SVG_TYPE = 'marcuscalidus-svg-panel' PIE_CHART_TYPE = 'grafana-piechart-panel' @@ -104,108 +104,108 @@ def to_json_data(self): SCHEMA_VERSION = 12 # Y Axis formats -DURATION_FORMAT = "dtdurations" -NO_FORMAT = "none" -OPS_FORMAT = "ops" -PERCENT_UNIT_FORMAT = "percentunit" -DAYS_FORMAT = "d" -HOURS_FORMAT = "h" -MINUTES_FORMAT = "m" -SECONDS_FORMAT = "s" -MILLISECONDS_FORMAT = "ms" -SHORT_FORMAT = "short" -BYTES_FORMAT = "bytes" -BITS_PER_SEC_FORMAT = "bps" -BYTES_PER_SEC_FORMAT = "Bps" -NONE_FORMAT = "none" -JOULE_FORMAT = "joule" -WATTHOUR_FORMAT = "watth" -WATT_FORMAT = "watt" -KWATT_FORMAT = "kwatt" -KWATTHOUR_FORMAT = "kwatth" -VOLT_FORMAT = "volt" -BAR_FORMAT = "pressurebar" -PSI_FORMAT = "pressurepsi" -CELSIUS_FORMAT = "celsius" -KELVIN_FORMAT = "kelvin" -GRAM_FORMAT = "massg" -EUR_FORMAT = "currencyEUR" -USD_FORMAT = "currencyUSD" -METER_FORMAT = "lengthm" -SQUARE_METER_FORMAT = "areaM2" -CUBIC_METER_FORMAT = "m3" -LITRE_FORMAT = "litre" -PERCENT_FORMAT = "percent" -VOLT_AMPERE_FORMAT = "voltamp" +DURATION_FORMAT = 'dtdurations' +NO_FORMAT = 'none' +OPS_FORMAT = 'ops' +PERCENT_UNIT_FORMAT = 'percentunit' +DAYS_FORMAT = 'd' +HOURS_FORMAT = 'h' +MINUTES_FORMAT = 'm' +SECONDS_FORMAT = 's' +MILLISECONDS_FORMAT = 'ms' +SHORT_FORMAT = 'short' +BYTES_FORMAT = 'bytes' +BITS_PER_SEC_FORMAT = 'bps' +BYTES_PER_SEC_FORMAT = 'Bps' +NONE_FORMAT = 'none' +JOULE_FORMAT = 'joule' +WATTHOUR_FORMAT = 'watth' +WATT_FORMAT = 'watt' +KWATT_FORMAT = 'kwatt' +KWATTHOUR_FORMAT = 'kwatth' +VOLT_FORMAT = 'volt' +BAR_FORMAT = 'pressurebar' +PSI_FORMAT = 'pressurepsi' +CELSIUS_FORMAT = 'celsius' +KELVIN_FORMAT = 'kelvin' +GRAM_FORMAT = 'massg' +EUR_FORMAT = 'currencyEUR' +USD_FORMAT = 'currencyUSD' +METER_FORMAT = 'lengthm' +SQUARE_METER_FORMAT = 'areaM2' +CUBIC_METER_FORMAT = 'm3' +LITRE_FORMAT = 'litre' +PERCENT_FORMAT = 'percent' +VOLT_AMPERE_FORMAT = 'voltamp' # Alert rule state -STATE_NO_DATA = "no_data" -STATE_ALERTING = "alerting" -STATE_KEEP_LAST_STATE = "keep_state" -STATE_OK = "ok" +STATE_NO_DATA = 'no_data' +STATE_ALERTING = 'alerting' +STATE_KEEP_LAST_STATE = 'keep_state' +STATE_OK = 'ok' # Evaluator -EVAL_GT = "gt" -EVAL_LT = "lt" -EVAL_WITHIN_RANGE = "within_range" -EVAL_OUTSIDE_RANGE = "outside_range" -EVAL_NO_VALUE = "no_value" +EVAL_GT = 'gt' +EVAL_LT = 'lt' +EVAL_WITHIN_RANGE = 'within_range' +EVAL_OUTSIDE_RANGE = 'outside_range' +EVAL_NO_VALUE = 'no_value' # Reducer Type # avg/min/max/sum/count/last/median/diff/percent_diff/count_non_null -RTYPE_AVG = "avg" -RTYPE_MIN = "min" -RTYPE_MAX = "max" -RTYPE_SUM = "sum" -RTYPE_COUNT = "count" -RTYPE_LAST = "last" -RTYPE_MEDIAN = "median" -RTYPE_DIFF = "diff" -RTYPE_PERCENT_DIFF = "percent_diff" -RTYPE_COUNT_NON_NULL = "count_non_null" +RTYPE_AVG = 'avg' +RTYPE_MIN = 'min' +RTYPE_MAX = 'max' +RTYPE_SUM = 'sum' +RTYPE_COUNT = 'count' +RTYPE_LAST = 'last' +RTYPE_MEDIAN = 'median' +RTYPE_DIFF = 'diff' +RTYPE_PERCENT_DIFF = 'percent_diff' +RTYPE_COUNT_NON_NULL = 'count_non_null' # Condition Type -CTYPE_QUERY = "query" +CTYPE_QUERY = 'query' # Operator -OP_AND = "and" -OP_OR = "or" +OP_AND = 'and' +OP_OR = 'or' # Text panel modes -TEXT_MODE_MARKDOWN = "markdown" -TEXT_MODE_HTML = "html" -TEXT_MODE_TEXT = "text" +TEXT_MODE_MARKDOWN = 'markdown' +TEXT_MODE_HTML = 'html' +TEXT_MODE_TEXT = 'text' # Datasource plugins -PLUGIN_ID_GRAPHITE = "graphite" -PLUGIN_ID_PROMETHEUS = "prometheus" -PLUGIN_ID_INFLUXDB = "influxdb" -PLUGIN_ID_OPENTSDB = "opentsdb" -PLUGIN_ID_ELASTICSEARCH = "elasticsearch" -PLUGIN_ID_CLOUDWATCH = "cloudwatch" +PLUGIN_ID_GRAPHITE = 'graphite' +PLUGIN_ID_PROMETHEUS = 'prometheus' +PLUGIN_ID_INFLUXDB = 'influxdb' +PLUGIN_ID_OPENTSDB = 'opentsdb' +PLUGIN_ID_ELASTICSEARCH = 'elasticsearch' +PLUGIN_ID_CLOUDWATCH = 'cloudwatch' # Target formats -TIME_SERIES_TARGET_FORMAT = "time_series" -TABLE_TARGET_FORMAT = "table" +TIME_SERIES_TARGET_FORMAT = 'time_series' +TABLE_TARGET_FORMAT = 'table' # Table Transforms -AGGREGATIONS_TRANSFORM = "timeseries_aggregations" -ANNOTATIONS_TRANSFORM = "annotations" -COLUMNS_TRANSFORM = "timeseries_to_columns" -JSON_TRANSFORM = "json" -ROWS_TRANSFORM = "timeseries_to_rows" -TABLE_TRANSFORM = "table" +AGGREGATIONS_TRANSFORM = 'timeseries_aggregations' +ANNOTATIONS_TRANSFORM = 'annotations' +COLUMNS_TRANSFORM = 'timeseries_to_columns' +JSON_TRANSFORM = 'json' +ROWS_TRANSFORM = 'timeseries_to_rows' +TABLE_TRANSFORM = 'table' # AlertList show selections -ALERTLIST_SHOW_CURRENT = "current" -ALERTLIST_SHOW_CHANGES = "changes" +ALERTLIST_SHOW_CURRENT = 'current' +ALERTLIST_SHOW_CHANGES = 'changes' # AlertList state filter options -ALERTLIST_STATE_OK = "ok" -ALERTLIST_STATE_PAUSED = "paused" -ALERTLIST_STATE_NO_DATA = "no_data" -ALERTLIST_STATE_EXECUTION_ERROR = "execution_error" -ALERTLIST_STATE_ALERTING = "alerting" +ALERTLIST_STATE_OK = 'ok' +ALERTLIST_STATE_PAUSED = 'paused' +ALERTLIST_STATE_NO_DATA = 'no_data' +ALERTLIST_STATE_EXECUTION_ERROR = 'execution_error' +ALERTLIST_STATE_ALERTING = 'alerting' # Display Sort Order SORT_ASC = 1 @@ -227,27 +227,27 @@ def to_json_data(self): SORT_ALPHA_IGNORE_CASE_ASC = 5 SORT_ALPHA_IGNORE_CASE_DESC = 6 -GAUGE_CALC_LAST = "last" -GAUGE_CALC_FIRST = "first" -GAUGE_CALC_MIN = "min" -GAUGE_CALC_MAX = "max" -GAUGE_CALC_MEAN = "mean" -GAUGE_CALC_TOTAL = "total" -GAUGE_CALC_COUNT = "count" -GAUGE_CALC_RANGE = "range" -GAUGE_CALC_DELTA = "delta" -GAUGE_CALC_STEP = "step" -GAUGE_CALC_DIFFERENCE = "difference" -GAUGE_CALC_LOGMIN = "logmin" -GAUGE_CALC_CHANGE_COUNT = "changeCount" -GAUGE_CALC_DISTINCT_COUNT = "distinctCount" - -ORIENTATION_HORIZONTAL = "horizontal" -ORIENTATION_VERTICAL = "vertical" - -GAUGE_DISPLAY_MODE_BASIC = "basic" -GAUGE_DISPLAY_MODE_LCD = "lcd" -GAUGE_DISPLAY_MODE_GRADIENT = "gradient" +GAUGE_CALC_LAST = 'last' +GAUGE_CALC_FIRST = 'first' +GAUGE_CALC_MIN = 'min' +GAUGE_CALC_MAX = 'max' +GAUGE_CALC_MEAN = 'mean' +GAUGE_CALC_TOTAL = 'total' +GAUGE_CALC_COUNT = 'count' +GAUGE_CALC_RANGE = 'range' +GAUGE_CALC_DELTA = 'delta' +GAUGE_CALC_STEP = 'step' +GAUGE_CALC_DIFFERENCE = 'difference' +GAUGE_CALC_LOGMIN = 'logmin' +GAUGE_CALC_CHANGE_COUNT = 'changeCount' +GAUGE_CALC_DISTINCT_COUNT = 'distinctCount' + +ORIENTATION_HORIZONTAL = 'horizontal' +ORIENTATION_VERTICAL = 'vertical' + +GAUGE_DISPLAY_MODE_BASIC = 'basic' +GAUGE_DISPLAY_MODE_LCD = 'lcd' +GAUGE_DISPLAY_MODE_GRADIENT = 'gradient' @attr.s @@ -266,20 +266,20 @@ def to_json_data(self): MAPPING_TYPE_VALUE_TO_TEXT = 1 MAPPING_TYPE_RANGE_TO_TEXT = 2 -MAPPING_VALUE_TO_TEXT = Mapping("value to text", MAPPING_TYPE_VALUE_TO_TEXT) -MAPPING_RANGE_TO_TEXT = Mapping("range to text", MAPPING_TYPE_RANGE_TO_TEXT) +MAPPING_VALUE_TO_TEXT = Mapping('value to text', MAPPING_TYPE_VALUE_TO_TEXT) +MAPPING_RANGE_TO_TEXT = Mapping('range to text', MAPPING_TYPE_RANGE_TO_TEXT) # Value types min/max/avg/current/total/name/first/delta/range -VTYPE_MIN = "min" -VTYPE_MAX = "max" -VTYPE_AVG = "avg" -VTYPE_CURR = "current" -VTYPE_TOTAL = "total" -VTYPE_NAME = "name" -VTYPE_FIRST = "first" -VTYPE_DELTA = "delta" -VTYPE_RANGE = "range" +VTYPE_MIN = 'min' +VTYPE_MAX = 'max' +VTYPE_AVG = 'avg' +VTYPE_CURR = 'current' +VTYPE_TOTAL = 'total' +VTYPE_NAME = 'name' +VTYPE_FIRST = 'first' +VTYPE_DELTA = 'delta' +VTYPE_RANGE = 'range' VTYPE_DEFAULT = VTYPE_AVG @@ -399,7 +399,7 @@ def to_json_data(self): def is_valid_xaxis_mode(instance, attribute, value): - XAXIS_MODES = ("time", "series") + XAXIS_MODES = ('time', 'series') if value not in XAXIS_MODES: raise ValueError("{attr} should be one of {choice}".format( attr=attribute, choice=XAXIS_MODES)) @@ -408,7 +408,7 @@ def is_valid_xaxis_mode(instance, attribute, value): @attr.s class XAxis(object): - mode = attr.ib(default="time", validator=is_valid_xaxis_mode) + mode = attr.ib(default='time', validator=is_valid_xaxis_mode) name = attr.ib(default=None) values = attr.ib(default=attr.Factory(list)) show = attr.ib(validator=instance_of(bool), default=True) @@ -590,12 +590,12 @@ class DataSourceInput(object): def to_json_data(self): return { - "description": self.description, - "label": self.label, - "name": self.name, - "pluginId": self.pluginId, - "pluginName": self.pluginName, - "type": "datasource", + 'description': self.description, + 'label': self.label, + 'name': self.name, + 'pluginId': self.pluginId, + 'pluginName': self.pluginName, + 'type': 'datasource', } @@ -608,11 +608,11 @@ class ConstantInput(object): def to_json_data(self): return { - "description": self.description, - "label": self.label, - "name": self.name, - "type": "constant", - "value": self.value, + 'description': self.description, + 'label': self.label, + 'name': self.name, + 'type': 'constant', + 'value': self.value, } @@ -630,24 +630,24 @@ class DashboardLink(object): def to_json_data(self): title = self.dashboard if self.title is None else self.title return { - "dashUri": self.uri, - "dashboard": self.dashboard, - "keepTime": self.keepTime, - "title": title, - "type": self.type, - "url": self.uri, + 'dashUri': self.uri, + 'dashboard': self.dashboard, + 'keepTime': self.keepTime, + 'title': title, + 'type': self.type, + 'url': self.uri, } @attr.s class ExternalLink(object): - '''ExternalLink creates a top-level link attached to a dashboard. + """ExternalLink creates a top-level link attached to a dashboard. :param url: the URL to link to :param title: the text of the link :param keepTime: if true, the URL params for the dashboard's current time period are appended - ''' + """ uri = attr.ib() title = attr.ib() keepTime = attr.ib( @@ -657,10 +657,10 @@ class ExternalLink(object): def to_json_data(self): return { - "keepTime": self.keepTime, - "title": self.title, - "type": 'link', - "url": self.uri, + 'keepTime': self.keepTime, + 'title': self.title, + 'type': 'link', + 'url': self.uri, } @@ -724,9 +724,9 @@ def __attrs_post_init__(self): for value in self.query.split(','): is_default = value == self.default option = { - "selected": is_default, - "text": value, - "value": value, + 'selected': is_default, + 'text': value, + 'value': value, } if is_default: self._current = option @@ -804,27 +804,27 @@ def to_json_data(self): DEFAULT_TIME_PICKER = TimePicker( refreshIntervals=[ - "5s", - "10s", - "30s", - "1m", - "5m", - "15m", - "30m", - "1h", - "2h", - "1d" + '5s', + '10s', + '30s', + '1m', + '5m', + '15m', + '30m', + '1h', + '2h', + '1d' ], timeOptions=[ - "5m", - "15m", - "1h", - "6h", - "12h", - "24h", - "2d", - "7d", - "30d" + '5m', + '15m', + '1h', + '6h', + '12h', + '24h', + '2d', + '7d', + '30d' ] ) @@ -836,8 +836,8 @@ class Evaluator(object): def to_json_data(self): return { - "type": self.type, - "params": self.params, + 'type': self.type, + 'params': self.params, } @@ -910,19 +910,19 @@ def to_json_data(self): self.target.refId, self.timeRange.from_time, self.timeRange.to_time ] return { - "evaluator": self.evaluator, - "operator": { - "type": self.operator, + 'evaluator': self.evaluator, + 'operator': { + 'type': self.operator, }, - "query": { - "model": self.target, - "params": queryParams, + 'query': { + 'model': self.target, + 'params': queryParams, }, - "reducer": { - "params": [], - "type": self.reducerType, + 'reducer': { + 'params': [], + 'type': self.reducerType, }, - "type": self.type, + 'type': self.type, } @@ -933,7 +933,7 @@ class Alert(object): message = attr.ib() alertConditions = attr.ib() executionErrorState = attr.ib(default=STATE_ALERTING) - frequency = attr.ib(default="60s") + frequency = attr.ib(default='60s') handler = attr.ib(default=1) noDataState = attr.ib(default=STATE_NO_DATA) notifications = attr.ib(default=attr.Factory(list)) @@ -941,15 +941,15 @@ class Alert(object): def to_json_data(self): return { - "conditions": self.alertConditions, - "executionErrorState": self.executionErrorState, - "frequency": self.frequency, - "handler": self.handler, - "message": self.message, - "name": self.name, - "noDataState": self.noDataState, - "notifications": self.notifications, - "for": self.gracePeriod, + 'conditions': self.alertConditions, + 'executionErrorState': self.executionErrorState, + 'frequency': self.frequency, + 'handler': self.handler, + 'message': self.message, + 'name': self.name, + 'noDataState': self.noDataState, + 'notifications': self.notifications, + 'for': self.gracePeriod, } @@ -1026,7 +1026,7 @@ def to_json_data(self): return { '__inputs': self.inputs, 'annotations': self.annotations, - "description": self.description, + 'description': self.description, 'editable': self.editable, 'gnetId': self.gnetId, 'hideControls': self.hideControls, @@ -1360,7 +1360,7 @@ class Stat(object): links = attr.ib(default=attr.Factory(list)) mappings = attr.ib(default=attr.Factory(list)) span = attr.ib(default=6) - thresholds = attr.ib(default='') + thresholds = attr.ib(default="") timeFrom = attr.ib(default=None) transparent = attr.ib(default=False, validator=instance_of(bool)) reduceCalc = attr.ib(default='mean', type=str) @@ -1429,7 +1429,7 @@ def to_json_data(self): mappingType = MAPPING_TYPE_VALUE_TO_TEXT if self.mapValue else MAPPING_TYPE_RANGE_TO_TEXT return { - 'operator': "", + 'operator': '', 'text': self.text, 'type': mappingType, 'value': self.mapValue, @@ -1544,7 +1544,7 @@ class SingleStat(object): description = attr.ib(default=None) decimals = attr.ib(default=None) editable = attr.ib(default=True, validator=instance_of(bool)) - format = attr.ib(default="none") + format = attr.ib(default='none') gauge = attr.ib(default=attr.Factory(Gauge), validator=instance_of(Gauge)) height = attr.ib(default=None) @@ -1562,11 +1562,11 @@ class SingleStat(object): maxDataPoints = attr.ib(default=100) minSpan = attr.ib(default=None) nullText = attr.ib(default=None) - nullPointMode = attr.ib(default="connected") + nullPointMode = attr.ib(default='connected') postfix = attr.ib(default="") - postfixFontSize = attr.ib(default="50%") + postfixFontSize = attr.ib(default='50%') prefix = attr.ib(default="") - prefixFontSize = attr.ib(default="50%") + prefixFontSize = attr.ib(default='50%') rangeMaps = attr.ib(default=attr.Factory(list)) repeat = attr.ib(default=None) span = attr.ib(default=6) @@ -1576,7 +1576,7 @@ class SingleStat(object): ) thresholds = attr.ib(default="") transparent = attr.ib(default=False, validator=instance_of(bool)) - valueFontSize = attr.ib(default="80%") + valueFontSize = attr.ib(default='80%') valueName = attr.ib(default=VTYPE_DEFAULT) valueMaps = attr.ib(default=attr.Factory(list)) timeFrom = attr.ib(default=None) @@ -1703,8 +1703,8 @@ class ColumnStyle(object): alias = attr.ib(default="") pattern = attr.ib(default="") - align = attr.ib(default="auto", validator=in_( - ["auto", "left", "right", "center"])) + align = attr.ib(default='auto', validator=in_( + ['auto', 'left', 'right', 'center'])) link = attr.ib(validator=instance_of(bool), default=False) linkOpenInNewTab = attr.ib(validator=instance_of(bool), default=False) linkUrl = attr.ib(validator=instance_of(str), default="") @@ -1753,8 +1753,8 @@ class Column(object): :param value: aggregation function """ - text = attr.ib(default="Avg") - value = attr.ib(default="avg") + text = attr.ib(default='Avg') + value = attr.ib(default='avg') def to_json_data(self): return { @@ -1824,7 +1824,7 @@ class Table(object): columns = attr.ib(default=attr.Factory(list)) description = attr.ib(default=None) editable = attr.ib(default=True, validator=instance_of(bool)) - fontSize = attr.ib(default="100%") + fontSize = attr.ib(default='100%') height = attr.ib(default=None) hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) id = attr.ib(default=None) @@ -1847,17 +1847,17 @@ class Table(object): def styles_default(self): return [ ColumnStyle( - alias="Time", - pattern="Time", + alias='Time', + pattern='Time', type=DateColumnStyleType(), ), ColumnStyle( - alias="time", - pattern="time", + alias='time', + pattern='time', type=DateColumnStyleType(), ), ColumnStyle( - pattern="/.*/", + pattern='/.*/', ), ] @@ -1966,7 +1966,7 @@ class BarGauge(object): ), ) editable = attr.ib(default=True, validator=instance_of(bool)) - format = attr.ib(default="none") + format = attr.ib(default='none') height = attr.ib(default=None) hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) id = attr.ib(default=None) @@ -1990,8 +1990,8 @@ class BarGauge(object): thresholds = attr.ib( default=attr.Factory( lambda: [ - Threshold("green", 0, 0.0), - Threshold("red", 1, 80.0) + Threshold('green', 0, 0.0), + Threshold('red', 1, 80.0) ] ), validator=instance_of(list), @@ -2003,47 +2003,47 @@ class BarGauge(object): def to_json_data(self): return { - "cacheTimeout": self.cacheTimeout, - "datasource": self.dataSource, - "description": self.description, - "editable": self.editable, - "height": self.height, - "hideTimeOverride": self.hideTimeOverride, - "id": self.id, - "interval": self.interval, - "links": self.links, - "maxDataPoints": self.maxDataPoints, - "minSpan": self.minSpan, - "options": { - "displayMode": self.displayMode, - "fieldOptions": { - "calcs": [self.calc], - "defaults": { - "decimals": self.decimals, - "max": self.max, - "min": self.min, - "title": self.label, - "unit": self.format, - "links": self.dataLinks, + 'cacheTimeout': self.cacheTimeout, + 'datasource': self.dataSource, + 'description': self.description, + 'editable': self.editable, + 'height': self.height, + 'hideTimeOverride': self.hideTimeOverride, + 'id': self.id, + 'interval': self.interval, + 'links': self.links, + 'maxDataPoints': self.maxDataPoints, + 'minSpan': self.minSpan, + 'options': { + 'displayMode': self.displayMode, + 'fieldOptions': { + 'calcs': [self.calc], + 'defaults': { + 'decimals': self.decimals, + 'max': self.max, + 'min': self.min, + 'title': self.label, + 'unit': self.format, + 'links': self.dataLinks, }, - "limit": self.limit, - "mappings": self.valueMaps, - "override": {}, - "thresholds": self.thresholds, - "values": self.allValues, + 'limit': self.limit, + 'mappings': self.valueMaps, + 'override': {}, + 'thresholds': self.thresholds, + 'values': self.allValues, }, - "orientation": self.orientation, - "showThresholdLabels": self.thresholdLabels, - "showThresholdMarkers": self.thresholdMarkers, + 'orientation': self.orientation, + 'showThresholdLabels': self.thresholdLabels, + 'showThresholdMarkers': self.thresholdMarkers, }, - "repeat": self.repeat, - "span": self.span, - "targets": self.targets, - "timeFrom": self.timeFrom, - "timeShift": self.timeShift, - "title": self.title, - "transparent": self.transparent, - "type": BARGAUGE_TYPE, + 'repeat': self.repeat, + 'span': self.span, + 'targets': self.targets, + 'timeFrom': self.timeFrom, + 'timeShift': self.timeShift, + 'title': self.title, + 'transparent': self.transparent, + 'type': BARGAUGE_TYPE, } @@ -2064,7 +2064,7 @@ class GaugePanel(object): :param hideTimeOverride: hides time overrides :param id: panel id :param interval: defines time interval between metric queries - :param labels: oprion to show gauge level labels + :param labels: option to show gauge level labels :param limit: limit of number of values to show when not Calculating :param links: additional web links :param max: maximum value of the gauge @@ -2095,7 +2095,7 @@ class GaugePanel(object): decimals = attr.ib(default=None) description = attr.ib(default=None) editable = attr.ib(default=True, validator=instance_of(bool)) - format = attr.ib(default="none") + format = attr.ib(default='none') height = attr.ib(default=None) hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) id = attr.ib(default=None) @@ -2115,8 +2115,8 @@ class GaugePanel(object): thresholds = attr.ib( default=attr.Factory( lambda: [ - Threshold("green", 0, 0.0), - Threshold("red", 1, 80.0) + Threshold('green', 0, 0.0), + Threshold('red', 1, 80.0) ] ), validator=instance_of(list), @@ -2128,45 +2128,45 @@ class GaugePanel(object): def to_json_data(self): return { - "cacheTimeout": self.cacheTimeout, - "datasource": self.dataSource, - "description": self.description, - "editable": self.editable, - "height": self.height, - "hideTimeOverride": self.hideTimeOverride, - "id": self.id, - "interval": self.interval, - "links": self.links, - "maxDataPoints": self.maxDataPoints, - "minSpan": self.minSpan, - "options": { - "fieldOptions": { - "calcs": [self.calc], - "defaults": { - "decimals": self.decimals, - "max": self.max, - "min": self.min, - "title": self.label, - "unit": self.format, - "links": self.dataLinks, + 'cacheTimeout': self.cacheTimeout, + 'datasource': self.dataSource, + 'description': self.description, + 'editable': self.editable, + 'height': self.height, + 'hideTimeOverride': self.hideTimeOverride, + 'id': self.id, + 'interval': self.interval, + 'links': self.links, + 'maxDataPoints': self.maxDataPoints, + 'minSpan': self.minSpan, + 'options': { + 'fieldOptions': { + 'calcs': [self.calc], + 'defaults': { + 'decimals': self.decimals, + 'max': self.max, + 'min': self.min, + 'title': self.label, + 'unit': self.format, + 'links': self.dataLinks, }, - "limit": self.limit, - "mappings": self.valueMaps, - "override": {}, - "thresholds": self.thresholds, - "values": self.allValues, + 'limit': self.limit, + 'mappings': self.valueMaps, + 'override': {}, + 'thresholds': self.thresholds, + 'values': self.allValues, }, - "showThresholdLabels": self.thresholdLabels, - "showThresholdMarkers": self.thresholdMarkers, + 'showThresholdLabels': self.thresholdLabels, + 'showThresholdMarkers': self.thresholdMarkers, }, - "repeat": self.repeat, - "span": self.span, - "targets": self.targets, - "timeFrom": self.timeFrom, - "timeShift": self.timeShift, - "title": self.title, - "transparent": self.transparent, - "type": GAUGE_TYPE, + 'repeat': self.repeat, + 'span': self.span, + 'targets': self.targets, + 'timeFrom': self.timeFrom, + 'timeShift': self.timeShift, + 'title': self.title, + 'transparent': self.transparent, + 'type': GAUGE_TYPE, } @@ -2188,19 +2188,19 @@ class HeatmapColor(object): colorScale = attr.ib(default='sqrt', validator=instance_of(str)) colorScheme = attr.ib(default='interpolateOranges') exponent = attr.ib(default=0.5, validator=instance_of(float)) - mode = attr.ib(default="spectrum", validator=instance_of(str)) + mode = attr.ib(default='spectrum', validator=instance_of(str)) max = attr.ib(default=None) min = attr.ib(default=None) def to_json_data(self): return { - "mode": self.mode, - "cardColor": self.cardColor, - "colorScale": self.colorScale, - "exponent": self.exponent, - "colorScheme": self.colorScheme, - "max": self.max, - "min": self.min, + 'mode': self.mode, + 'cardColor': self.cardColor, + 'colorScale': self.colorScale, + 'exponent': self.exponent, + 'colorScheme': self.colorScheme, + 'max': self.max, + 'min': self.min, } @@ -2227,7 +2227,7 @@ class Heatmap(object): description = attr.ib(default=None) id = attr.ib(default=None) # The below does not really like the Legend class we have defined above - legend = attr.ib(default={"show": False}) + legend = attr.ib(default={'show': False}) links = attr.ib(default=None) targets = attr.ib(default=None) tooltip = attr.ib( @@ -2238,8 +2238,8 @@ class Heatmap(object): cards = attr.ib( default={ - "cardPadding": None, - "cardRound": None + 'cardPadding': None, + 'cardRound': None } ) @@ -2322,21 +2322,21 @@ class StatusmapColor(object): colorScale = attr.ib(default='sqrt', validator=instance_of(str)) colorScheme = attr.ib(default='GnYlRd', validator=instance_of(str)) exponent = attr.ib(default=0.5, validator=instance_of(float)) - mode = attr.ib(default="spectrum", validator=instance_of(str)) + mode = attr.ib(default='spectrum', validator=instance_of(str)) thresholds = attr.ib(default=[], validator=instance_of(list)) max = attr.ib(default=None) min = attr.ib(default=None) def to_json_data(self): return { - "mode": self.mode, - "cardColor": self.cardColor, - "colorScale": self.colorScale, - "exponent": self.exponent, - "colorScheme": self.colorScheme, - "max": self.max, - "min": self.min, - "thresholds": self.thresholds + 'mode': self.mode, + 'cardColor': self.cardColor, + 'colorScale': self.colorScale, + 'exponent': self.exponent, + 'colorScheme': self.colorScheme, + 'max': self.max, + 'min': self.min, + 'thresholds': self.thresholds } @@ -2345,7 +2345,7 @@ class Statusmap(object): """Generates json structure for the flant-statusmap-panel visualisation plugin (https://grafana.com/grafana/plugins/flant-statusmap-panel/). :param alert - :param cards: A statusmap card object: keys "cardRound", "cardMinWidth", "cardHSpacing", "cardVSpacing" + :param cards: A statusmap card object: keys 'cardRound', 'cardMinWidth', 'cardHSpacing', 'cardVSpacing' :param color: A StatusmapColor object :param dataSource: Name of the datasource to use :param description: Description of the panel @@ -2373,10 +2373,10 @@ class Statusmap(object): alert = attr.ib(default=None) cards = attr.ib( default={ - "cardRound": None, - "cardMinWidth": 5, - "cardHSpacing": 2, - "cardVSpacing": 2, + 'cardRound': None, + 'cardMinWidth': 5, + 'cardHSpacing': 2, + 'cardVSpacing': 2, }, validator=instance_of(dict)) color = attr.ib( @@ -2467,7 +2467,7 @@ class Svg(object): title = attr.ib() description = attr.ib(default=None) editable = attr.ib(default=True, validator=instance_of(bool)) - format = attr.ib(default="none") + format = attr.ib(default='none') jsCodeFilePath = attr.ib(default="", validator=instance_of(str)) jsCodeInitFilePath = attr.ib(default="", validator=instance_of(str)) height = attr.ib(default=None) @@ -2483,7 +2483,7 @@ def read_file(file_path): read_data = f.read() return read_data else: - return "" + return '' def to_json_data(self): @@ -2545,7 +2545,7 @@ class PieChart(object): showLegend = attr.ib(default=True) showLegendValues = attr.ib(default=True) span = attr.ib(default=6) - thresholds = attr.ib(default='') + thresholds = attr.ib(default="") timeFrom = attr.ib(default=None) transparent = attr.ib(default=False, validator=instance_of(bool)) @@ -2592,17 +2592,17 @@ class Threshold(object): index = attr.ib(validator=instance_of(int)) value = attr.ib(validator=instance_of(float)) line = attr.ib(default=True, validator=instance_of(bool)) - op = attr.ib(default="gt") - yaxis = attr.ib(default="left") + op = attr.ib(default=EVAL_GT) + yaxis = attr.ib(default='left') def to_json_data(self): return { - "op": self.op, - "yaxis": self.yaxis, - "color": self.color, - "line": self.line, - "index": self.index, - "value": "null" if self.index == 0 else self.value, + 'op': self.op, + 'yaxis': self.yaxis, + 'color': self.color, + 'line': self.line, + 'index': self.index, + 'value': 'null' if self.index == 0 else self.value, } @@ -2615,9 +2615,9 @@ class SeriesOverride(object): def to_json_data(self): return { - "alias": self.alias, - "bars": self.bars, - "lines": self.lines, - "yaxis": self.yaxis, - "color": self.color, + 'alias': self.alias, + 'bars': self.bars, + 'lines': self.lines, + 'yaxis': self.yaxis, + 'color': self.color, } diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index d1d08218..d18cdd4d 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -4,9 +4,9 @@ import itertools from attr.validators import instance_of -DATE_HISTOGRAM_DEFAULT_FIELD = "time_iso8601" -ORDER_ASC = "asc" -ORDER_DESC = "desc" +DATE_HISTOGRAM_DEFAULT_FIELD = 'time_iso8601' +ORDER_ASC = 'asc' +ORDER_DESC = 'desc' @attr.s @@ -98,11 +98,11 @@ class AverageMetricAgg(object): def to_json_data(self): return { 'id': str(self.id), - "hide": self.hide, - "type": "avg", - "field": self.field, - "settings": {}, - "meta": {} + 'hide': self.hide, + 'type': 'avg', + 'field': self.field, + 'settings': {}, + 'meta': {} } @@ -127,7 +127,7 @@ class DerivativeMetricAgg(object): def to_json_data(self): settings = {} if self.unit != "": - settings["unit"] = self.unit + settings['unit'] = self.unit return { 'id': str(self.id), @@ -178,7 +178,7 @@ class DateHistogramGroupBy(object): default=DATE_HISTOGRAM_DEFAULT_FIELD, validator=instance_of(str), ) - interval = attr.ib(default="auto", validator=instance_of(str)) + interval = attr.ib(default='auto', validator=instance_of(str)) minDocCount = attr.ib(default=0, validator=instance_of(int)) def to_json_data(self): @@ -215,8 +215,8 @@ def to_json_data(self): pipelineVars = [] for field in self.fields: pipelineVars.append({ - "name": str(field), - "pipelineAgg": str(self.fields[field]) + 'name': str(field), + 'pipelineAgg': str(self.fields[field]) }) return { @@ -288,7 +288,7 @@ class TermsGroupBy(object): id = attr.ib(default=0, validator=instance_of(int)) minDocCount = attr.ib(default=1, validator=instance_of(int)) order = attr.ib(default=ORDER_DESC, validator=instance_of(str)) - orderBy = attr.ib(default="_term", validator=instance_of(str)) + orderBy = attr.ib(default='_term', validator=instance_of(str)) size = attr.ib(default=0, validator=instance_of(int)) def to_json_data(self): diff --git a/grafanalib/influxdb.py b/grafanalib/influxdb.py index fc59a561..51638d28 100644 --- a/grafanalib/influxdb.py +++ b/grafanalib/influxdb.py @@ -2,7 +2,7 @@ import attr -TIME_SERIES_TARGET_FORMAT = "time_series" +TIME_SERIES_TARGET_FORMAT = 'time_series' @attr.s diff --git a/grafanalib/opentsdb.py b/grafanalib/opentsdb.py index 9576e895..955b7dc2 100644 --- a/grafanalib/opentsdb.py +++ b/grafanalib/opentsdb.py @@ -5,36 +5,36 @@ from grafanalib.validators import is_in # OpenTSDB aggregators -OTSDB_AGG_AVG = "avg" -OTSDB_AGG_COUNT = "count" -OTSDB_AGG_DEV = "dev" -OTSDB_AGG_EP50R3 = "ep50r3" -OTSDB_AGG_EP50R7 = "ep50r7" -OTSDB_AGG_EP75R3 = "ep75r3" -OTSDB_AGG_EP75R7 = "ep75r7" -OTSDB_AGG_EP90R3 = "ep90r3" -OTSDB_AGG_EP90R7 = "ep90r7" -OTSDB_AGG_EP95R3 = "ep95r3" -OTSDB_AGG_EP95R7 = "ep95r7" -OTSDB_AGG_EP99R3 = "ep99r3" -OTSDB_AGG_EP99R7 = "ep99r7" -OTSDB_AGG_EP999R3 = "ep999r3" -OTSDB_AGG_EP999R7 = "ep999r7" -OTSDB_AGG_FIRST = "first" -OTSDB_AGG_LAST = "last" -OTSDB_AGG_MIMMIN = "mimmin" -OTSDB_AGG_MIMMAX = "mimmax" -OTSDB_AGG_MIN = "min" -OTSDB_AGG_MAX = "max" -OTSDB_AGG_NONE = "none" -OTSDB_AGG_P50 = "p50" -OTSDB_AGG_P75 = "p75" -OTSDB_AGG_P90 = "p90" -OTSDB_AGG_P95 = "p95" -OTSDB_AGG_P99 = "p99" -OTSDB_AGG_P999 = "p999" -OTSDB_AGG_SUM = "sum" -OTSDB_AGG_ZIMSUM = "zimsum" +OTSDB_AGG_AVG = 'avg' +OTSDB_AGG_COUNT = 'count' +OTSDB_AGG_DEV = 'dev' +OTSDB_AGG_EP50R3 = 'ep50r3' +OTSDB_AGG_EP50R7 = 'ep50r7' +OTSDB_AGG_EP75R3 = 'ep75r3' +OTSDB_AGG_EP75R7 = 'ep75r7' +OTSDB_AGG_EP90R3 = 'ep90r3' +OTSDB_AGG_EP90R7 = 'ep90r7' +OTSDB_AGG_EP95R3 = 'ep95r3' +OTSDB_AGG_EP95R7 = 'ep95r7' +OTSDB_AGG_EP99R3 = 'ep99r3' +OTSDB_AGG_EP99R7 = 'ep99r7' +OTSDB_AGG_EP999R3 = 'ep999r3' +OTSDB_AGG_EP999R7 = 'ep999r7' +OTSDB_AGG_FIRST = 'first' +OTSDB_AGG_LAST = 'last' +OTSDB_AGG_MIMMIN = 'mimmin' +OTSDB_AGG_MIMMAX = 'mimmax' +OTSDB_AGG_MIN = 'min' +OTSDB_AGG_MAX = 'max' +OTSDB_AGG_NONE = 'none' +OTSDB_AGG_P50 = 'p50' +OTSDB_AGG_P75 = 'p75' +OTSDB_AGG_P90 = 'p90' +OTSDB_AGG_P95 = 'p95' +OTSDB_AGG_P99 = 'p99' +OTSDB_AGG_P999 = 'p999' +OTSDB_AGG_SUM = 'sum' +OTSDB_AGG_ZIMSUM = 'zimsum' OTSDB_DOWNSAMPLING_FILL_POLICIES = ('none', 'nan', 'null', 'zero') OTSDB_DOWNSAMPLING_FILL_POLICY_DEFAULT = 'none' @@ -106,7 +106,7 @@ class OpenTSDBTarget(object): metric = attr.ib() refId = attr.ib(default="") - aggregator = attr.ib(default="sum") + aggregator = attr.ib(default='sum') alias = attr.ib(default=None) isCounter = attr.ib(default=False, validator=instance_of(bool)) counterMax = attr.ib(default=None) diff --git a/grafanalib/weave.py b/grafanalib/weave.py index bb5ec8ac..d8313589 100644 --- a/grafanalib/weave.py +++ b/grafanalib/weave.py @@ -10,20 +10,20 @@ from grafanalib import prometheus -YELLOW = "#EAB839" -GREEN = "#7EB26D" -BLUE = "#6ED0E0" -ORANGE = "#EF843C" -RED = "#E24D42" +YELLOW = '#EAB839' +GREEN = '#7EB26D' +BLUE = '#6ED0E0' +ORANGE = '#EF843C' +RED = '#E24D42' ALIAS_COLORS = { - "1xx": YELLOW, - "2xx": GREEN, - "3xx": BLUE, - "4xx": ORANGE, - "5xx": RED, - "success": GREEN, - "error": RED, + '1xx': YELLOW, + '2xx': GREEN, + '3xx': BLUE, + '4xx': ORANGE, + '5xx': RED, + 'success': GREEN, + 'error': RED, } diff --git a/grafanalib/zabbix.py b/grafanalib/zabbix.py index 8939c7aa..fa8ca9d1 100644 --- a/grafanalib/zabbix.py +++ b/grafanalib/zabbix.py @@ -7,65 +7,65 @@ RGBA, Percent, Pixels, DashboardLink, DEFAULT_ROW_HEIGHT, BLANK, GREEN) -ZABBIX_TRIGGERS_TYPE = "alexanderzobnin-zabbix-triggers-panel" +ZABBIX_TRIGGERS_TYPE = 'alexanderzobnin-zabbix-triggers-panel' ZABBIX_QMODE_METRICS = 0 ZABBIX_QMODE_SERVICES = 1 ZABBIX_QMODE_TEXT = 2 ZABBIX_SLA_PROP_STATUS = { - "name": "Status", - "property": "status"} + 'name': 'Status', + 'property': 'status'} ZABBIX_SLA_PROP_SLA = { - "name": "SLA", - "property": "sla"} + 'name': 'SLA', + 'property': 'sla'} ZABBIX_SLA_PROP_OKTIME = { - "name": "OK time", - "property": "okTime"} + 'name': 'OK time', + 'property': 'okTime'} ZABBIX_SLA_PROP_PROBTIME = { - "name": "Problem time", - "property": "problemTime"} + 'name': 'Problem time', + 'property': 'problemTime'} ZABBIX_SLA_PROP_DOWNTIME = { - "name": "Down time", - "property": "downtimeTime", + 'name': 'Down time', + 'property': 'downtimeTime', } ZABBIX_EVENT_PROBLEMS = { - "text": "Problems", - "value": [1]} + 'text': 'Problems', + 'value': [1]} ZABBIX_EVENT_OK = { - "text": "OK", - "value": [0]} + 'text': 'OK', + 'value': [0]} ZABBIX_EVENT_ALL = { - "text": "All", - "value": [0, 1]} + 'text': 'All', + 'value': [0, 1]} -ZABBIX_TRIGGERS_SHOW_ALL = "all triggers" -ZABBIX_TRIGGERS_SHOW_ACK = "acknowledged" -ZABBIX_TRIGGERS_SHOW_NACK = "unacknowledged" +ZABBIX_TRIGGERS_SHOW_ALL = 'all triggers' +ZABBIX_TRIGGERS_SHOW_ACK = 'acknowledged' +ZABBIX_TRIGGERS_SHOW_NACK = 'unacknowledged' ZABBIX_SORT_TRIGGERS_BY_CHANGE = { - "text": "last change", - "value": "lastchange", + 'text': 'last change', + 'value': 'lastchange', } ZABBIX_SORT_TRIGGERS_BY_SEVERITY = { - "text": "severity", - "value": "priority", + 'text': 'severity', + 'value': 'priority', } ZABBIX_SEVERITY_COLORS = ( - ("#B7DBAB", "Not classified"), - ("#82B5D8", "Information"), - ("#E5AC0E", "Warning"), - ("#C15C17", "Average"), - ("#BF1B00", "High"), - ("#890F02", "Disaster"), + ('#B7DBAB', 'Not classified'), + ('#82B5D8', 'Information'), + ('#E5AC0E', 'Warning'), + ('#C15C17', 'Average'), + ('#BF1B00', 'High'), + ('#890F02', 'Disaster'), ) @@ -81,7 +81,7 @@ class ZabbixTargetOptions(object): def to_json_data(self): return { - "showDisabledItems": self.showDisabledItems + 'showDisabledItems': self.showDisabledItems } @@ -91,7 +91,7 @@ class ZabbixTargetField(object): def to_json_data(self): return { - "filter": self.filter + 'filter': self.filter } @@ -147,23 +147,23 @@ class ZabbixTarget(object): def to_json_data(self): obj = { - "application": ZabbixTargetField(self.application), - "expr": self.expr, - "functions": self.functions, - "group": ZabbixTargetField(self.group), - "host": ZabbixTargetField(self.host), - "intervalFactor": self.intervalFactor, - "item": ZabbixTargetField(self.item), - "mode": self.mode, - "options": self.options, - "refId": self.refId, + 'application': ZabbixTargetField(self.application), + 'expr': self.expr, + 'functions': self.functions, + 'group': ZabbixTargetField(self.group), + 'host': ZabbixTargetField(self.host), + 'intervalFactor': self.intervalFactor, + 'item': ZabbixTargetField(self.item), + 'mode': self.mode, + 'options': self.options, + 'refId': self.refId, } if self.mode == ZABBIX_QMODE_SERVICES: - obj["slaProperty"] = self.slaProperty, - obj["itservice"] = {"name": self.itService} + obj['slaProperty'] = self.slaProperty, + obj['itservice'] = {'name': self.itService} if self.mode == ZABBIX_QMODE_TEXT: - obj["textFilter"] = self.textFilter - obj["useCaptureGroups"] = self.useCaptureGroups + obj['textFilter'] = self.textFilter + obj['useCaptureGroups'] = self.useCaptureGroups return obj @@ -179,15 +179,15 @@ class ZabbixDeltaFunction(object): def to_json_data(self): text = "delta()" definition = { - "category": "Transform", - "name": "delta", - "defaultParams": [], - "params": []} + 'category': 'Transform', + 'name': 'delta', + 'defaultParams': [], + 'params': []} return { - "added": self.added, - "text": text, - "def": definition, - "params": [], + 'added': self.added, + 'text': text, + 'def': definition, + 'params': [], } @@ -200,9 +200,9 @@ class ZabbixGroupByFunction(object): http://docs.grafana-zabbix.org/reference/functions/#groupBy """ - _options = ("avg", "min", "max", "median") - _default_interval = "1m" - _default_function = "avg" + _options = ('avg', 'min', 'max', 'median') + _default_interval = '1m' + _default_function = 'avg' added = attr.ib(default=False, validator=instance_of(bool)) interval = attr.ib(default=_default_interval, validator=is_interval) @@ -212,24 +212,24 @@ class ZabbixGroupByFunction(object): def to_json_data(self): text = "groupBy({interval}, {function})" definition = { - "category": "Transform", - "name": "groupBy", - "defaultParams": [ + 'category': 'Transform', + 'name': 'groupBy', + 'defaultParams': [ self._default_interval, self._default_function, ], - "params": [ - {"name": "interval", - "type": "string"}, - {"name": "function", - "options": self._options, - "type": "string"}]} + 'params': [ + {'name': 'interval', + 'type': 'string'}, + {'name': 'function', + 'options': self._options, + 'type': 'string'}]} return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( interval=self.interval, function=self.function), - "params": [self.interval, self.function], - "added": self.added, + 'params': [self.interval, self.function], + 'added': self.added, } @@ -249,19 +249,19 @@ class ZabbixScaleFunction(object): def to_json_data(self): text = "scale({factor})" definition = { - "category": "Transform", - "name": "scale", - "defaultParams": [self._default_factor], - "params": [ - {"name": "factor", - "options": [100, 0.01, 10, -1], - "type": "float"}] + 'category': 'Transform', + 'name': 'scale', + 'defaultParams': [self._default_factor], + 'params': [ + {'name': 'factor', + 'options': [100, 0.01, 10, -1], + 'type': 'float'}] } return { - "def": definition, - "text": text.format(factor=self.factor), - "params": [self.factor], - "added": self.added, + 'def': definition, + 'text': text.format(factor=self.factor), + 'params': [self.factor], + 'added': self.added, } @@ -275,9 +275,9 @@ class ZabbixAggregateByFunction(object): http://docs.grafana-zabbix.org/reference/functions/#aggregateBy """ - _options = ("avg", "min", "max", "median") - _default_interval = "1m" - _default_function = "avg" + _options = ('avg', 'min', 'max', 'median') + _default_interval = '1m' + _default_function = 'avg' added = attr.ib(default=False, validator=instance_of(bool)) interval = attr.ib(default=_default_interval, validator=is_interval) @@ -287,24 +287,24 @@ class ZabbixAggregateByFunction(object): def to_json_data(self): text = "aggregateBy({interval}, {function})" definition = { - "category": "Aggregate", - "name": "aggregateBy", - "defaultParams": [ + 'category': 'Aggregate', + 'name': 'aggregateBy', + 'defaultParams': [ self._default_interval, self._default_function, ], - "params": [ - {"name": "interval", - "type": "string"}, - {"name": "function", - "options": self._options, - "type": "string"}]} + 'params': [ + {'name': 'interval', + 'type': 'string'}, + {'name': 'function', + 'options': self._options, + 'type': 'string'}]} return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( interval=self.interval, function=self.function), - "params": [self.interval, self.function], - "added": self.added, + 'params': [self.interval, self.function], + 'added': self.added, } @@ -316,7 +316,7 @@ class ZabbixAverageFunction(object): http://docs.grafana-zabbix.org/reference/functions/#average """ - _default_interval = "1m" + _default_interval = '1m' added = attr.ib(default=False, validator=instance_of(bool)) interval = attr.ib(default=_default_interval, validator=is_interval) @@ -324,21 +324,21 @@ class ZabbixAverageFunction(object): def to_json_data(self): text = "average({interval})" definition = { - "category": "Aggregate", + 'category': "Aggregate", "name": "average", "defaultParams": [ self._default_interval, ], - "params": [ - {"name": "interval", - "type": "string"}] + 'params': [ + {'name': 'interval', + 'type': 'string'}] } return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( interval=self.interval), - "params": [self.interval], - "added": self.added, + 'params': [self.interval], + 'added': self.added, } @@ -350,7 +350,7 @@ class ZabbixMaxFunction(object): http://docs.grafana-zabbix.org/reference/functions/#max """ - _default_interval = "1m" + _default_interval = '1m' added = attr.ib(default=False, validator=instance_of(bool)) interval = attr.ib(default=_default_interval, validator=is_interval) @@ -358,21 +358,21 @@ class ZabbixMaxFunction(object): def to_json_data(self): text = "max({interval})" definition = { - "category": "Aggregate", - "name": "max", - "defaultParams": [ + 'category': 'Aggregate', + 'name': 'max', + 'defaultParams': [ self._default_interval, ], - "params": [ - {"name": "interval", - "type": "string"}] + 'params': [ + {'name': 'interval', + 'type': 'string'}] } return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( interval=self.interval), - "params": [self.interval], - "added": self.added, + 'params': [self.interval], + 'added': self.added, } @@ -384,29 +384,29 @@ class ZabbixMedianFunction(object): http://docs.grafana-zabbix.org/reference/functions/#median """ - _default_interval = "1m" + _default_interval = '1m' added = attr.ib(default=False, validator=instance_of(bool)) - interval = attr.ib(default="1m", validator=is_interval) + interval = attr.ib(default='1m', validator=is_interval) def to_json_data(self): text = "median({interval})" definition = { - "category": "Aggregate", - "name": "median", - "defaultParams": [ + 'category': 'Aggregate', + 'name': 'median', + 'defaultParams': [ self._default_interval, ], - "params": [ - {"name": "interval", - "type": "string"}] + 'params': [ + {'name': 'interval', + 'type': 'string'}] } return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( interval=self.interval), - "params": [self.interval], - "added": self.added, + 'params': [self.interval], + 'added': self.added, } @@ -418,7 +418,7 @@ class ZabbixMinFunction(object): http://docs.grafana-zabbix.org/reference/functions/#min """ - _default_interval = "1m" + _default_interval = '1m' added = attr.ib(default=False, validator=instance_of(bool)) interval = attr.ib(default=_default_interval, validator=is_interval) @@ -426,21 +426,21 @@ class ZabbixMinFunction(object): def to_json_data(self): text = "min({interval})" definition = { - "category": "Aggregate", - "name": "min", - "defaultParams": [ + 'category': 'Aggregate', + 'name': 'min', + 'defaultParams': [ self._default_interval, ], - "params": [ - {"name": "interval", - "type": "string"}] + 'params': [ + {'name': 'interval', + 'type': 'string'}] } return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( interval=self.interval), - "params": [self.interval], - "added": self.added, + 'params': [self.interval], + 'added': self.added, } @@ -459,24 +459,24 @@ class ZabbixSumSeriesFunction(object): def to_json_data(self): text = "sumSeries()" definition = { - "category": "Aggregate", - "name": "sumSeries", - "defaultParams": [], - "params": []} + 'category': 'Aggregate', + 'name': 'sumSeries', + 'defaultParams': [], + 'params': []} return { - "added": self.added, - "text": text, - "def": definition, - "params": [], + 'added': self.added, + 'text': text, + 'def': definition, + 'params': [], } @attr.s class ZabbixBottomFunction(object): - _options = ("avg", "min", "max", "median") + _options = ('avg', 'min', 'max', 'median') _default_number = 5 - _default_function = "avg" + _default_function = 'avg' added = attr.ib(default=False, validator=instance_of(bool)) number = attr.ib(default=_default_number, validator=instance_of(int)) @@ -486,33 +486,33 @@ class ZabbixBottomFunction(object): def to_json_data(self): text = "bottom({number}, {function})" definition = { - "category": "Filter", - "name": "bottom", - "defaultParams": [ + 'category': 'Filter', + 'name': 'bottom', + 'defaultParams': [ self._default_number, self._default_function, ], - "params": [ - {"name": "number", - "type": "string"}, - {"name": "function", - "options": self._options, - "type": "string"}]} + 'params': [ + {'name': 'number', + 'type': 'string'}, + {'name': 'function', + 'options': self._options, + 'type': 'string'}]} return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( number=self.number, function=self.function), - "params": [self.number, self.function], - "added": self.added, + 'params': [self.number, self.function], + 'added': self.added, } @attr.s class ZabbixTopFunction(object): - _options = ("avg", "min", "max", "median") + _options = ('avg', 'min', 'max', 'median') _default_number = 5 - _default_function = "avg" + _default_function = 'avg' added = attr.ib(default=False, validator=instance_of(bool)) number = attr.ib(default=_default_number, validator=instance_of(int)) @@ -522,24 +522,24 @@ class ZabbixTopFunction(object): def to_json_data(self): text = "top({number}, {function})" definition = { - "category": "Filter", - "name": "top", - "defaultParams": [ + 'category': 'Filter', + 'name': 'top', + 'defaultParams': [ self._default_number, self._default_function, ], - "params": [ - {"name": "number", - "type": "string"}, - {"name": "function", - "options": self._options, - "type": "string"}]} + 'params': [ + {'name': 'number', + 'type': 'string'}, + {'name': 'function', + 'options': self._options, + 'type': 'string'}]} return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( number=self.number, function=self.function), - "params": [self.number, self.function], - "added": self.added, + 'params': [self.number, self.function], + 'added': self.added, } @@ -553,7 +553,7 @@ class ZabbixTrendValueFunction(object): """ _options = ('avg', 'min', 'max') - _default_type = "avg" + _default_type = 'avg' added = attr.ib(default=False, validator=instance_of(bool)) type = attr.ib(default=_default_type, validator=is_in(_options)) @@ -561,21 +561,21 @@ class ZabbixTrendValueFunction(object): def to_json_data(self): text = "trendValue({type})" definition = { - "category": "Trends", - "name": "trendValue", - "defaultParams": [ + 'category': 'Trends', + 'name': 'trendValue', + 'defaultParams': [ self._default_type, ], - "params": [ - {"name": "type", - "options": self._options, - "type": "string"}]} + 'params': [ + {'name': 'type', + 'options': self._options, + 'type': 'string'}]} return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( type=self.type), - "params": [self.type], - "added": self.added, + 'params': [self.type], + 'added': self.added, } @@ -590,8 +590,8 @@ class ZabbixTimeShiftFunction(object): http://docs.grafana-zabbix.org/reference/functions/#timeShift """ - _options = ("24h", "7d", "1M", "+24h", "-24h") - _default_interval = "24h" + _options = ('24h', '7d', '1M', '+24h', '-24h') + _default_interval = '24h' added = attr.ib(default=False, validator=instance_of(bool)) interval = attr.ib(default=_default_interval) @@ -599,21 +599,21 @@ class ZabbixTimeShiftFunction(object): def to_json_data(self): text = "timeShift({interval})" definition = { - "category": "Time", - "name": "timeShift", - "defaultParams": [ + 'category': 'Time', + 'name': 'timeShift', + 'defaultParams': [ self._default_interval, ], - "params": [ - {"name": "interval", - "options": self._options, - "type": "string"}]} + 'params': [ + {'name': 'interval', + 'options': self._options, + 'type': 'string'}]} return { - "def": definition, - "text": text.format( + 'def': definition, + 'text': text.format( interval=self.interval), - "params": [self.interval], - "added": self.added, + 'params': [self.interval], + 'added': self.added, } @@ -630,17 +630,17 @@ class ZabbixSetAliasFunction(object): def to_json_data(self): text = "setAlias({alias})" definition = { - "category": "Alias", - "name": "setAlias", - "defaultParams": [], - "params": [ - {"name": "alias", - "type": "string"}]} + 'category': 'Alias', + 'name': 'setAlias', + 'defaultParams': [], + 'params': [ + {'name': 'alias', + 'type': 'string'}]} return { - "def": definition, - "text": text.format(alias=self.alias), - "params": [self.alias], - "added": self.added, + 'def': definition, + 'text': text.format(alias=self.alias), + 'params': [self.alias], + 'added': self.added, } @@ -658,17 +658,17 @@ class ZabbixSetAliasByRegexFunction(object): def to_json_data(self): text = "setAliasByRegex({regexp})" definition = { - "category": "Alias", - "name": "setAliasByRegex", - "defaultParams": [], - "params": [ - {"name": "aliasByRegex", - "type": "string"}]} + 'category': 'Alias', + 'name': 'setAliasByRegex', + 'defaultParams': [], + 'params': [ + {'name': 'aliasByRegex', + 'type': 'string'}]} return { - "def": definition, - "text": text.format(regexp=self.regexp), - "params": [self.regexp], - "added": self.added, + 'def': definition, + 'text': text.format(regexp=self.regexp), + 'params': [self.regexp], + 'added': self.added, } @@ -713,10 +713,10 @@ class ZabbixColor(object): def to_json_data(self): return { - "color": self.color, - "priority": self.priority, - "severity": self.severity, - "show": self.show, + 'color': self.color, + 'priority': self.priority, + 'severity': self.severity, + 'show': self.show, } @@ -730,10 +730,10 @@ class ZabbixTrigger(object): def to_json_data(self): return { - "application": ZabbixTargetField(self.application), - "group": ZabbixTargetField(self.group), - "host": ZabbixTargetField(self.host), - "trigger": ZabbixTargetField(self.trigger), + 'application': ZabbixTargetField(self.application), + 'group': ZabbixTargetField(self.group), + 'host': ZabbixTargetField(self.host), + 'trigger': ZabbixTargetField(self.trigger), } @@ -830,37 +830,37 @@ class ZabbixTriggersPanel(object): def to_json_data(self): return { - "type": ZABBIX_TRIGGERS_TYPE, - "datasource": self.dataSource, - "title": self.title, - "ackEventColor": self.ackEventColor, - "ageField": self.ageField, - "customLastChangeFormat": self.customLastChangeFormat, - "description": self.description, - "fontSize": self.fontSize, - "height": self.height, - "hideHostsInMaintenance": self.hideHostsInMaintenance, - "hostField": self.hostField, - "hostTechNameField": self.hostTechNameField, - "id": self.id, - "infoField": self.infoField, - "lastChangeField": self.lastChangeField, - "lastChangeFormat": self.lastChangeFormat, - "limit": self.limit, - "links": self.links, - "markAckEvents": self.markAckEvents, - "minSpan": self.minSpan, - "okEventColor": self.okEventColor, - "pageSize": self.pageSize, - "repeat": self.repeat, - "scroll": self.scroll, - "severityField": self.severityField, - "showEvents": self.showEvents, - "showTriggers": self.showTriggers, - "sortTriggersBy": self.sortTriggersBy, - "span": self.span, - "statusField": self.statusField, - "transparent": self.transparent, - "triggers": self.triggers, - "triggerSeverity": self.triggerSeverity, + 'type': ZABBIX_TRIGGERS_TYPE, + 'datasource': self.dataSource, + 'title': self.title, + 'ackEventColor': self.ackEventColor, + 'ageField': self.ageField, + 'customLastChangeFormat': self.customLastChangeFormat, + 'description': self.description, + 'fontSize': self.fontSize, + 'height': self.height, + 'hideHostsInMaintenance': self.hideHostsInMaintenance, + 'hostField': self.hostField, + 'hostTechNameField': self.hostTechNameField, + 'id': self.id, + 'infoField': self.infoField, + 'lastChangeField': self.lastChangeField, + 'lastChangeFormat': self.lastChangeFormat, + 'limit': self.limit, + 'links': self.links, + 'markAckEvents': self.markAckEvents, + 'minSpan': self.minSpan, + 'okEventColor': self.okEventColor, + 'pageSize': self.pageSize, + 'repeat': self.repeat, + 'scroll': self.scroll, + 'severityField': self.severityField, + 'showEvents': self.showEvents, + 'showTriggers': self.showTriggers, + 'sortTriggersBy': self.sortTriggersBy, + 'span': self.span, + 'statusField': self.statusField, + 'transparent': self.transparent, + 'triggers': self.triggers, + 'triggerSeverity': self.triggerSeverity, } From 0c1736b81b774cf8dfd576d72e21e7f944fddfae Mon Sep 17 00:00:00 2001 From: James Gibson Date: Mon, 2 Nov 2020 16:02:33 +0000 Subject: [PATCH 153/403] Add note to contributor docs about quote coding style --- docs/CONTRIBUTING.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst index 1991c406..f054a63e 100644 --- a/docs/CONTRIBUTING.rst +++ b/docs/CONTRIBUTING.rst @@ -33,6 +33,9 @@ Conventions * Local variables are ``snake_cased`` * We're kind of fussy about indentation: 4 spaces everywhere, follow the examples in `core.py`_ if you're uncertain +* Triple Double quotes `"""` for docstrings +* Double quotes "" for human readable message or when string used for interpolation +* Single quotes '' for symbol like strings Testing ------- From 306c81f7b9dff9f550fa9d339c51c8c08be23dd4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2020 06:56:01 +0000 Subject: [PATCH 154/403] Bump sphinx from 3.2.1 to 3.3.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.2.1 to 3.3.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.2.1...v3.3.0) Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index a856123c..23719a34 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.2.1 +sphinx == 3.3.0 sphinx_rtd_theme == 0.5.0 From 920cc34147c58e57474960ca2e09ad56ae1596a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Nov 2020 06:48:41 +0000 Subject: [PATCH 155/403] Bump actions/checkout from v2.3.3 to v2.3.4 Bumps [actions/checkout](https://github.com/actions/checkout) from v2.3.3 to v2.3.4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.3.3...5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f) Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index a09cc570..6e91d590 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -7,7 +7,7 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.3 + - uses: actions/checkout@v2.3.4 - name: Set up Python uses: actions/setup-python@v2.1.4 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 0d8b7c58..f23e63c0 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -7,7 +7,7 @@ jobs: name: Build and publish Python 🐍 distributions 📦 to PyPI runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2.3.3 + - uses: actions/checkout@v2.3.4 - name: Set up Python 3.7 uses: actions/setup-python@v2.1.4 with: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 8d07020e..8a82e49e 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: matrix: python: [3.6, 3.7, 3.8, 3.9] steps: - - uses: actions/checkout@v2.3.3 + - uses: actions/checkout@v2.3.4 - name: Set up Python uses: actions/setup-python@v2.1.4 with: From bb39568f78cbf510f9856db0d9defe9e43a3c2d9 Mon Sep 17 00:00:00 2001 From: Lautaro Mazzitelli Date: Fri, 6 Nov 2020 00:18:42 +0100 Subject: [PATCH 156/403] Adding Alert Threshold flag to graphs. --- grafanalib/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index 6716d4ce..20f1c018 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1061,6 +1061,7 @@ class Graph(object): title = attr.ib() targets = attr.ib() + alertThreshold = attr.ib(default=True, validator=instance_of(bool)) aliasColors = attr.ib(default=attr.Factory(dict)) bars = attr.ib(default=False, validator=instance_of(bool)) dataLinks = attr.ib(default=attr.Factory(list)) @@ -1126,6 +1127,7 @@ def to_json_data(self): 'nullPointMode': self.nullPointMode, 'options': { 'dataLinks': self.dataLinks, + 'alertThreshold': self.alertThreshold, }, 'percentage': self.percentage, 'pointradius': self.pointRadius, From 7c20a34a400c879f0cc8ce8a13a57b19189e037e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Nov 2020 06:47:33 +0000 Subject: [PATCH 157/403] Bump attrs from 19.2 to 20.3.0 Bumps [attrs](https://github.com/python-attrs/attrs) from 19.2 to 20.3.0. - [Release notes](https://github.com/python-attrs/attrs/releases) - [Changelog](https://github.com/python-attrs/attrs/blob/master/CHANGELOG.rst) - [Commits](https://github.com/python-attrs/attrs/compare/19.2.0...20.3.0) Signed-off-by: dependabot[bot] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 71e84452..2a3216b6 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ def local_file(name): 'Topic :: System :: Monitoring', ], install_requires=[ - 'attrs==19.2', + 'attrs==20.3.0', ], extras_require={ 'dev': [ From c3e1e24fe800e895c357e88df69ff684a54214ac Mon Sep 17 00:00:00 2001 From: Lautaro Mazzitelli Date: Mon, 9 Nov 2020 10:08:36 +0100 Subject: [PATCH 158/403] Added changelog entry --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 80391186..3e8d2323 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,8 @@ Changelog 0.5.9 (TBD) =========== +* Added Alert Threshold enabled/disabled to Graphs. + Changes ------- From 2144d726bea05e4f4132d9bf195ee42e3df6ac85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Nov 2020 06:48:49 +0000 Subject: [PATCH 159/403] Bump sphinx from 3.3.0 to 3.3.1 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.3.0 to 3.3.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.3.0...v3.3.1) Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 23719a34..f7531db7 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.3.0 +sphinx == 3.3.1 sphinx_rtd_theme == 0.5.0 From 19ba92acac6500bbba02144b85555bab4e6bf283 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Mon, 16 Nov 2020 15:33:41 +0000 Subject: [PATCH 160/403] Add Data unit formats to YAxis constants (https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/valueFormats/categories.ts) --- CHANGELOG.rst | 1 + grafanalib/core.py | 2 +- grafanalib/formatunits.py | 237 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 grafanalib/formatunits.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3e8d2323..1b42c853 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ Changelog =========== * Added Alert Threshold enabled/disabled to Graphs. +* Added constants for all Grafana value formats Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index 20f1c018..e03a9371 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -103,7 +103,7 @@ def to_json_data(self): SCHEMA_VERSION = 12 -# Y Axis formats +# (DEPRECATED: use formatunits.py) Y Axis formats DURATION_FORMAT = 'dtdurations' NO_FORMAT = 'none' OPS_FORMAT = 'ops' diff --git a/grafanalib/formatunits.py b/grafanalib/formatunits.py new file mode 100644 index 00000000..0b8ad1ec --- /dev/null +++ b/grafanalib/formatunits.py @@ -0,0 +1,237 @@ +""" +Grafana unit formats +(https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/valueFormats/categories.ts) + +To use: +from grafanalib import formatunits as UNITS + +format = UNITS.BYTES +""" + +NO_FORMAT = 'none' +NONE_FORMAT = 'none' +PERCENT_UNIT = 'percentunit' +PERCENT_FORMAT = 'percent' +SHORT = 'short' +# Acceleration +METERS_SEC_2 = 'accMS2' # m/sec² +FEET_SEC_2 = 'accFS2' # f/sec² +G_UNIT = 'accG' # g +# Angle +DEGREES = 'degree' # ° +RADIANS = 'radian' # rad +GRADIAN = 'grad' # grad +ARC_MINUTES = 'arcmin' # arcmin +ARC_SECONDS = 'arcsec' # arcsec +# Area +SQUARE_METERS = 'areaM2' # m² +SQUARE_FEET = 'areaF2' # ft² +SQUARE_MILES = 'areaMI2' # mi² +# Computation +FLOPS_PER_SEC = 'flops' # FLOP/s +MEGA_FLOPS_PER_SEC = 'mflops' # MFLOP/s +GIGA_FLOPS_PER_SEC = 'gflops' # GFLOP/s +TERA_FLOPS_PER_SEC = 'tflops' # TFLOP/s +PETA_FLOPS_PER_SEC = 'pflops' # PFLOP/s +EXA_FLOPS_PER_SEC = 'eflops' # EFLOP/s +ZETTA_FLOPS_PER_SEC = 'zflops' # ZFLOP/s +YOTTA_FLOPS_PER_SEC = 'yflops' # YFLOP/s +# Concentration +PARTS_PER_MILLION = 'ppm' # ppm +PARTS_PER_BILLION = 'conppb' # ppb +NANO_GRAM_PER_CUBIC_METER = 'conngm3' # ng/m³ +NANO_GRAM_PER_NORMAL_CUBIC_METER = 'conngNm3' # ng/Nm³ +MICRO_GRAM_PER_CUBIC_METER = 'conμgm3' # μg/m³ +MICRO_GRAM_PER_NORMAL_CUBIC_METER = 'conμgNm3' # μg/Nm³ +MILLI_GRAM_PER_CUBIC_METER = 'conmgm3' # mg/m³ +MILLI_GRAM_PER_NORMAL_CUBIC_METER = 'conmgNm3' # mg/Nm³ +GRAM_PER_CUBIC_METER = 'congm3' # g/m³ +GRAM_PER_NORMAL_CUBIC_METER = 'congNm3' # g/Nm³ +MILLI_GRAM_PER_DECI_LITRE = 'conmgdL' # mg/dL +MILLI_MOLES_PER_LITRE = 'conmmolL' # mmol/L +# Currency +DOLLARS = 'currencyUSD' # $ +POUNDS = 'currencyGBP' # £ +EURO = 'currencyEUR' # € +YEN = 'currencyJPY' # ¥ +RUBLES = 'currencyRUB' # ₽ +HRYVNIAS = 'currencyUAH' # ₴ +REAL = 'currencyBRL' # R$ +DANISH_KRONE = 'currencyDKK' # kr +ICELANDIC_KRONA = 'currencyISK' # kr +NORWEGIAN_KRONE = 'currencyNOK' # kr +SWEDISH_KORNA = 'currencySEK' # kr +CZECH_KORUNA = 'currencyCZK' # czk +SWISS_FRANC = 'currencyCHF' # CHF +POLISH_ZLOTY = 'currencyPLN' # PLN +BITCOIN = 'currencyBTC' # ฿ +MILLI_BITCOIN = 'currencymBTC' # mBTC +MICRO_BITCOIN = 'currencyμBTC' # μBTC +SOUTH_AFRICAN_RAND = 'currencyZAR' # R +INDIAN_RUPEE = 'currencyINR' # ₹ +SOUTH_KOREAN_WON = 'currencyKRW' # ₩ +INDONESIAN_RUPIAH = 'currencyIDR' # Rp +PHILIPPINE_PESO = 'currencyPHP' # PHP +# Data (metric) +BYTES = 'decbytes' # B +KILO_BYTES = 'deckbytes' # kB +MEGA_BYTES = 'decmbytes' # MB +GIGA_BYTES = 'decgbytes' # GB +TERA_BYTES = 'dectbytes' # TB +PETA_BYTES = 'decpbytes' # PB +# Data Rate +PACKETS_SEC = 'pps' # p/s +BYTES_SEC = 'Bps' # B/s +KILO_BYTES_SEC = 'KBs' # kB/s +MEGA_BYTES_SEC = 'MBs' # MB/s +GIGA_BYTES_SEC = 'GBs' # GB/s +TERA_BYTES_SEC = 'TBs' # TB/s +PETA_BYTES_SEC = 'PBs' # PB/s +BITS_SEC = 'bps' # b/s +KILO_BITS_SEC = 'Kbits' # kb/s +MEGA_BITS_SEC = 'Mbits' # Mb/s +GIGA_BITS_SEC = 'Gbits' # Gb/s +TERA_BITS_SEC = 'Tbits' # Tb/s +PETA_BITS_SEC = 'Pbits' # Pb/s +# Energy +WATT = 'watt' # W +KILO_WATT = 'kwatt' # kW +MEGA_WATT = 'megwatt' # MW +GAGA_WATT = 'gwatt' # GW +MILLI_WATT = 'mwatt' # mW +WATT_SQUARE_METER = 'Wm2' # W/m² +VOLT_AMPERE = 'voltamp' # VA +KILO_VOLT_AMPERE = 'kvoltamp' # kVA +VAR = 'voltampreact' # VAR +KILO_VAR = 'kvoltampreact' # kVAR +WATT_HOUR = 'watth' # Wh +WATT_HOUR_KILO = 'watthperkg' # Wh/kg +KILO_WATT_HOUR = 'kwatth' # kWh +AMPERE_HOUR = 'amph' # Ah +KILO_AMPERE_HR = 'kamph' # kAh +MILLI_AMPER_HOUR = 'mamph' # mAh +JOULE = 'joule' # J +ELECTRON_VOLT = 'ev' # eV +AMPERE = 'amp' # A +KILO_AMPERE = 'kamp' # kA +MILLI_AMPERE = 'mamp' # mA +VOLT = 'volt' # V +KILO_VOLT = 'kvolt' # kV +MILLI_VOLT = 'mvolt' # mV +DECIBEL_MILLI_WATT = 'dBm' # dBm +OHM = 'ohm' # Ω +KILO_OHM = 'kohm' # kΩ +MEGA_OHM = 'Mohm' # MΩ +FARAD = 'farad' # F +MICRO_FARAD = 'µfarad' # µF +NANO_FARAD = 'nfarad' # nF +PICO_FARAD = 'pfarad' # pF +FEMTO_FARAD = 'ffarad' # fF +HENRY = 'henry' # H +MILLI_HENRY = 'mhenry' # mH +MICRO_HENRY = 'µhenry' # µH +LUMENS = 'lumens' # Lm +# Flow +GALLONS_PER_MIN = 'flowgpm' # gpm +CUBIC_METERS_PER_SEC = 'flowcms' # cms +CUBIC_FEET_PER_SEC = 'flowcfs' # cfs +CUBIC_FEET_PER_MIN = 'flowcfm' # cfm +LITRES_PER_HOUR = 'litreh' # L/h +LITRES_PER_MIN = 'flowlpm' # L/min +MILLI_LITRE_PER_MIN = 'flowmlpm' # mL/min +LUX = 'lux' # lx +# Force +NEWTON_METERS = 'forceNm' # Nm +KILO_NEWTON_METERS = 'forcekNm' # kNm +NEWTONS = 'forceN' # N +KILO_NEWTONS = 'forcekN' # kN +# Hash Rate +HASHES_PER_SEC = 'Hs' # H/s +KILO_HASHES_PER_SEC = 'KHs' # kH/s +MEGA_HASHES_PER_SEC = 'MHs' # MH/s +GIGA_HASHES_PER_SEC = 'GHs' # GH/s +TERA_HASHES_PER_SEC = 'THs' # TH/s +PETA_HASHES_PER_SEC = 'PHs' # PH/s +EXA_HASHES_PER_SEC = 'EHs' # EH/s +# Mass +MILLI_GRAM = 'massmg' # mg +GRAM = 'massg' # g +KILO_GRAM = 'masskg' # kg +METRIC_TON = 'masst' # t +# Length +MILLI_METER = 'lengthmm' # mm +METER = 'lengthm' # m +KILO_METER = 'lengthkm' # km +FEET = 'lengthft' # ft +MILE = 'lengthmi' # mi +# Pressure +MILLI_BARS = 'pressurembar' # mBar, +BARS = 'pressurebar' # Bar, +KILO_BARS = 'pressurekbar' # kBar, +PASCALS = 'pressurepa' # Pa +HECTO_PASCALS = 'pressurehpa' # hPa +KILO_PASCALS = 'pressurekpa' # kPa +INCHES_OF_MERCURY = 'pressurehg' # "Hg +PSI = 'pressurepsi' # psi +# Radiation +BECQUEREL = 'radbq' # Bq +CURIE = 'radci' # Ci +GRAY = 'radgy' # Gy +RAD = 'radrad' # rad +MICROSIEVERT = 'radusv' # µSv +MILLI_SIEVERT = 'radmsv' # mSv +SIEVERT = 'radsv' # Sv +REM = 'radrem' # rem +EXPOSURE = 'radexpckg' # C/kg +ROENTGEN = 'radr' # R +MICRO_SIEVERT_PER_HOUR = 'radusvh' # µSv/h +MILLI_SIEVERT_PER_HOUR = 'radmsvh' # mSv/h +SIEVERT_PER_HOUR = 'radsvh' # Sv/h +# Rotational Speed +RPM = 'rotrpm' # rpm +HERTZ_ROTATION = 'rothz' # Hz +RADS_PER_SEC = 'rotrads' # rad/s +DEGREES_PER_SECOND = 'rotdegs' # °/s +# Temperature +CELSUIS = 'celsius' # °C +FARENHEIT = 'fahrenheit' # °F +KELVIN = 'kelvin' # K +# Time +HERTZ = 'hertz' # Hz +NANO_SECONDS = 'ns' # ns +MICRO_SECONDS = 'µs' # µs +MILLI_SECONDS = 'ms' # ms +SECONDS = 's' # s +MINUTES = 'm' # m +HOURS = 'h' # h +DAYS = 'd' # d +DURATION_MILLI_SECONDS = 'dtdurationms' # ms +DURATION_SECONDS = 'dtdurations' # s +HH_MM_SS = 'dthms' # hh:mm:ss +D_HH_MM_SS = 'dtdhms' # d hh:mm:ss +TIME_TICKS = 'timeticks' # s/100 +CLOCK_MSEC = 'clockms' # ms +CLOCK_SEC = 'clocks' # s +# Throughput +COUNTS_PER_SEC = 'cps' # cps +OPS_PER_SEC = 'ops' # ops +REQUESTS_PER_SEC = 'reqps' # rps +READS_PER_SEC = 'rps' # rps +WRITES_PER_SEC = 'wps' # wps +IO_OPS_PER_SEC = 'iops' # iops +COUNTS_PER_MIN = 'cpm' # cpm +OPS_PER_MIN = 'opm' # opm +READS_PER_MIN = 'rpm' # rpm +WRITES_PER_MIN = 'wpm' # wpm +# Velocity +METERS_PER_SEC = 'velocityms' # m/s +KILO_METERS_PER_SEC = 'velocitykmh' # km/h +MILES_PER_HOUR = 'velocitymph' # mph +KNOTS = 'velocityknot' # kn +# Volume +MILLI_LITRE = 'mlitre' # mL +LITRE = 'litre' # L +CUBIC_METER = 'm3' # m³ +NORMAIL_CUBIC_METER = 'Nm3' # Nm³ +CUBIC_DECI_METER = 'dm3' # dm³ +GALLONS = 'gallons' # g From 48eb0d4acaef5f20d265d0ec90e953c6ea802e57 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Fri, 20 Nov 2020 12:01:59 +0000 Subject: [PATCH 161/403] Add white colour constant --- grafanalib/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index e03a9371..b3ba89f4 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -61,6 +61,7 @@ def to_json_data(self): ORANGE = RGBA(237, 129, 40, 0.89) RED = RGBA(245, 54, 54, 0.9) BLANK = RGBA(0, 0, 0, 0.0) +WHITE = RGB(255, 255, 255) INDIVIDUAL = 'individual' CUMULATIVE = 'cumulative' From 15a8c5071134fdb3b76a1534c786e3d9f2155146 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 20 Nov 2020 16:39:02 +0100 Subject: [PATCH 162/403] probably sufficient to check weekly --- .github/dependabot.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ae1d01ab..b2335345 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,9 +8,9 @@ updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: - interval: "daily" + interval: "weekly" - package-ecosystem: "github-actions" # See documentation for possible values directory: "/" # Location of package manifests schedule: - interval: "daily" + interval: "weekly" From ed65674b603736f3d7763fc4a3874cecadc23499 Mon Sep 17 00:00:00 2001 From: "Ritz, Bruno" Date: Fri, 20 Nov 2020 20:41:56 +0100 Subject: [PATCH 163/403] Add support for repetition to the Stat panel --- grafanalib/core.py | 25 +++++++++++++++++++++++++ grafanalib/tests/test_core.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index b3ba89f4..7cc5906f 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -346,6 +346,26 @@ def to_json_data(self): } +def is_valid_max_per_row(instance, attribute, value): + if ((value != None) and not isinstance(value, int)): + raise ValueError("{attr} should either be None or an integer".format( + attr=attribute)) + + +@attr.s +class Repeat(object): + """ + Panel repetition settings. + + :param direction: The direction into which to repeat ('h' or 'v') + :param variable: The name of the variable over whose values to repeat + :param maxPerRow: The maximum number of panels per row in horizontal repetition + """ + + direction = attr.ib(default=None) + variable = attr.ib(default=None) + maxPerRow = attr.ib(default=None, validator=is_valid_max_per_row) + @attr.s class Target(object): """ @@ -1346,6 +1366,7 @@ class Stat(object): :param span: defines the number of spans that will be used for panel :param thresholds: single stat thresholds :param transparent: defines if the panel should be transparent + :param repeat: defines how the panel should be repeated """ dataSource = attr.ib() @@ -1368,6 +1389,7 @@ class Stat(object): transparent = attr.ib(default=False, validator=instance_of(bool)) reduceCalc = attr.ib(default='mean', type=str) decimals = attr.ib(default=None) + repeat = attr.ib(default=attr.Factory(Repeat), validator=instance_of(Repeat)) def to_json_data(self): return { @@ -1408,6 +1430,9 @@ def to_json_data(self): 'transparent': self.transparent, 'type': STAT_TYPE, 'timeFrom': self.timeFrom, + 'repeat': self.repeat.variable, + 'repeatDirection': self.repeat.direction, + 'maxPerRow': self.repeat.maxPerRow } diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 854d1f6e..ff244ddf 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -79,6 +79,39 @@ def test_table_styled_columns(): ] +def test_stat_no_repeat(): + t = G.Stat( + title='dummy', + dataSource='data source', + targets=[ + G.Target(expr='some expr') + ] + ) + + assert t.to_json_data()['repeat'] == None + assert t.to_json_data()['repeatDirection'] == None + assert t.to_json_data()['maxPerRow'] == None + + +def test_stat_with_repeat(): + t = G.Stat( + title='dummy', + dataSource='data source', + targets=[ + G.Target(expr='some expr') + ], + repeat=G.Repeat( + variable="repetitionVariable", + direction='h', + maxPerRow=10 + ) + ) + + assert t.to_json_data()['repeat'] == 'repetitionVariable' + assert t.to_json_data()['repeatDirection'] == 'h' + assert t.to_json_data()['maxPerRow'] == 10 + + def test_single_stat(): data_source = 'dummy data source' targets = ['dummy_prom_query'] From 828ea3e1f2f30d5e2114a4566ac83c4abafa9b89 Mon Sep 17 00:00:00 2001 From: "Ritz, Bruno" Date: Fri, 20 Nov 2020 21:12:32 +0100 Subject: [PATCH 164/403] Fix indendation --- grafanalib/core.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 7cc5906f..045bf9c7 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -354,17 +354,18 @@ def is_valid_max_per_row(instance, attribute, value): @attr.s class Repeat(object): - """ - Panel repetition settings. + """ + Panel repetition settings. + + :param direction: The direction into which to repeat ('h' or 'v') + :param variable: The name of the variable over whose values to repeat + :param maxPerRow: The maximum number of panels per row in horizontal repetition + """ - :param direction: The direction into which to repeat ('h' or 'v') - :param variable: The name of the variable over whose values to repeat - :param maxPerRow: The maximum number of panels per row in horizontal repetition - """ + direction = attr.ib(default=None) + variable = attr.ib(default=None) + maxPerRow = attr.ib(default=None, validator=is_valid_max_per_row) - direction = attr.ib(default=None) - variable = attr.ib(default=None) - maxPerRow = attr.ib(default=None, validator=is_valid_max_per_row) @attr.s class Target(object): From 6972a676fbe37fe3e161d04c0316c7f1eaeb6fc3 Mon Sep 17 00:00:00 2001 From: "Ritz, Bruno" Date: Fri, 20 Nov 2020 21:15:24 +0100 Subject: [PATCH 165/403] Fix linter warning --- grafanalib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 045bf9c7..d5a95027 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -347,7 +347,7 @@ def to_json_data(self): def is_valid_max_per_row(instance, attribute, value): - if ((value != None) and not isinstance(value, int)): + if ((value is not None) and not isinstance(value, int)): raise ValueError("{attr} should either be None or an integer".format( attr=attribute)) From 14252e4e47d614ccb79edc650c34b8d300f677ca Mon Sep 17 00:00:00 2001 From: "Ritz, Bruno" Date: Fri, 20 Nov 2020 21:20:06 +0100 Subject: [PATCH 166/403] Fix linter warnings in test --- grafanalib/tests/test_core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index ff244ddf..2d8f571e 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -88,9 +88,9 @@ def test_stat_no_repeat(): ] ) - assert t.to_json_data()['repeat'] == None - assert t.to_json_data()['repeatDirection'] == None - assert t.to_json_data()['maxPerRow'] == None + assert t.to_json_data()['repeat'] is None + assert t.to_json_data()['repeatDirection'] is None + assert t.to_json_data()['maxPerRow'] is None def test_stat_with_repeat(): From e85654ca4bdfc60147521b845d563fb8b8c103b8 Mon Sep 17 00:00:00 2001 From: "Ritz, Bruno" Date: Mon, 23 Nov 2020 11:10:49 +0100 Subject: [PATCH 167/403] Update change log --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1b42c853..f5b2491b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ Changelog * Added Alert Threshold enabled/disabled to Graphs. * Added constants for all Grafana value formats +* Added support for repetitions to Stat Panels (https://grafana.com/docs/grafana/latest/variables/repeat-panels-or-rows/) Changes ------- From 7683a43c987325c51aacdd5f194b175c8ba45f6b Mon Sep 17 00:00:00 2001 From: Yevhen Kainara Date: Wed, 2 Dec 2020 12:39:49 +0200 Subject: [PATCH 168/403] add hide parameter to Target --- grafanalib/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index d5a95027..d3cc15f6 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -377,6 +377,7 @@ class Target(object): expr = attr.ib(default="") format = attr.ib(default=TIME_SERIES_TARGET_FORMAT) + hide = attr.ib(default=False, validator=instance_of(bool)) legendFormat = attr.ib(default="") interval = attr.ib(default="", validator=instance_of(str)) intervalFactor = attr.ib(default=2) @@ -392,6 +393,7 @@ def to_json_data(self): 'expr': self.expr, 'target': self.target, 'format': self.format, + 'hide': self.hide, 'interval': self.interval, 'intervalFactor': self.intervalFactor, 'legendFormat': self.legendFormat, From 8b9261bd0636464f6e74723b4b46a389734f65dd Mon Sep 17 00:00:00 2001 From: Yevhen Kainara Date: Wed, 2 Dec 2020 20:16:13 +0200 Subject: [PATCH 169/403] add changelog notice --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f5b2491b..c6bedb05 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ Changes ------- * Change supported python versions from 3.6 to 3.9 +* Added hide parameter to Target 0.5.8 (2020-11-02) ================== From 55446cee49c9ec11bb05da02a305f0178a742f57 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Wed, 9 Dec 2020 08:30:37 +0000 Subject: [PATCH 170/403] Add missing attr.s decorator to SeriesOverride --- grafanalib/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index d3cc15f6..b38aa215 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2637,6 +2637,7 @@ def to_json_data(self): } +@attr.s class SeriesOverride(object): alias = attr.ib() bars = attr.ib(default=False) From beb2d72c22b8da438669b6864bec1e1b66a28a41 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Wed, 16 Dec 2020 10:22:51 +0000 Subject: [PATCH 171/403] Add missing inherited argument to tests --- grafanalib/tests/test_validators.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grafanalib/tests/test_validators.py b/grafanalib/tests/test_validators.py index 254a0496..e3bf144e 100644 --- a/grafanalib/tests/test_validators.py +++ b/grafanalib/tests/test_validators.py @@ -14,7 +14,8 @@ def create_attribute(): eq=True, order=False, hash=True, - init=True) + init=True, + inherited=False) def test_is_in(): From 6bf370050f2d1cb36d4d86456eb54ec9b5a64c09 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Wed, 16 Dec 2020 12:19:45 +0000 Subject: [PATCH 172/403] Create panel object for all panels to inherit --- CHANGELOG.rst | 1 + grafanalib/core.py | 707 ++++++++++++++++++--------------------------- 2 files changed, 280 insertions(+), 428 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c6bedb05..5f58df66 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ Changelog * Added Alert Threshold enabled/disabled to Graphs. * Added constants for all Grafana value formats * Added support for repetitions to Stat Panels (https://grafana.com/docs/grafana/latest/variables/repeat-panels-or-rows/) +* Add Panel object for all panels to inherit from Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index b38aa215..bbd2e411 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1073,7 +1073,77 @@ def to_json_data(self): @attr.s -class Graph(object): +class Panel(object): + """ + Generic panel for shared defaults + :param cacheTimeout: metric query result cache ttl + :param dataSource: Grafana datasource name + :param description: optional panel description + :param editable: defines if panel is editable via web interfaces + :param height: defines panel height + :param hideTimeOverride: hides time overrides + :param id: panel id + :param interval: defines time interval between metric queries + :param links: additional web links + :param maxDataPoints: maximum metric query results, + that will be used for rendering + :param minSpan: minimum span number + :param repeat: Template's name to repeat Graph on + :param span: defines the number of spans that will be used for panel + :param targets: list of metric requests for chosen datasource + :param timeFrom: time range that Override relative time + :param title: of the panel + :param transparent: defines if panel should be transparent + """ + + dataSource = attr.ib(default=None) + targets = attr.ib(default=attr.Factory(list), validator=instance_of(list)) + title = attr.ib(default="") + cacheTimeout = attr.ib(default=None) + description = attr.ib(default=None) + editable = attr.ib(default=True, validator=instance_of(bool)) + error = attr.ib(default=False, validator=instance_of(bool)) + height = attr.ib(default=None) + hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) + id = attr.ib(default=None) + interval = attr.ib(default=None) + links = attr.ib(default=attr.Factory(list)) + maxDataPoints = attr.ib(default=100) + minSpan = attr.ib(default=None) + repeat = attr.ib(default=None) + span = attr.ib(default=None) + timeFrom = attr.ib(default=None) + timeShift = attr.ib(default=None) + transparent = attr.ib(default=False, validator=instance_of(bool)) + + def panel_json(self, overrides): + res = { + "cacheTimeout": self.cacheTimeout, + "datasource": self.dataSource, + "description": self.description, + "editable": self.editable, + "error": self.error, + "height": self.height, + "hideTimeOverride": self.hideTimeOverride, + "id": self.id, + "interval": self.interval, + "links": self.links, + "maxDataPoints": self.maxDataPoints, + "minSpan": self.minSpan, + "repeat": self.repeat, + "span": self.span, + "targets": self.targets, + "timeFrom": self.timeFrom, + "timeShift": self.timeShift, + "title": self.title, + "transparent": self.transparent, + } + res.update(overrides) + return res + + +@attr.s +class Graph(Panel): """ Generates Graph panel json structure. @@ -1083,19 +1153,13 @@ class Graph(object): :param repeat: Template's name to repeat Graph on """ - title = attr.ib() - targets = attr.ib() alertThreshold = attr.ib(default=True, validator=instance_of(bool)) aliasColors = attr.ib(default=attr.Factory(dict)) bars = attr.ib(default=False, validator=instance_of(bool)) dataLinks = attr.ib(default=attr.Factory(list)) - dataSource = attr.ib(default=None) - description = attr.ib(default=None) - editable = attr.ib(default=True, validator=instance_of(bool)) error = attr.ib(default=False, validator=instance_of(bool)) fill = attr.ib(default=1, validator=instance_of(int)) grid = attr.ib(default=attr.Factory(Grid), validator=instance_of(Grid)) - id = attr.ib(default=None) isNew = attr.ib(default=True, validator=instance_of(bool)) legend = attr.ib( default=attr.Factory(Legend), @@ -1103,25 +1167,18 @@ class Graph(object): ) lines = attr.ib(default=True, validator=instance_of(bool)) lineWidth = attr.ib(default=DEFAULT_LINE_WIDTH) - links = attr.ib(default=attr.Factory(list)) - minSpan = attr.ib(default=None) nullPointMode = attr.ib(default=NULL_CONNECTED) percentage = attr.ib(default=False, validator=instance_of(bool)) pointRadius = attr.ib(default=DEFAULT_POINT_RADIUS) points = attr.ib(default=False, validator=instance_of(bool)) renderer = attr.ib(default=DEFAULT_RENDERER) - repeat = attr.ib(default=None) seriesOverrides = attr.ib(default=attr.Factory(list)) - span = attr.ib(default=None) stack = attr.ib(default=False, validator=instance_of(bool)) steppedLine = attr.ib(default=False, validator=instance_of(bool)) - timeFrom = attr.ib(default=None) - timeShift = attr.ib(default=None) tooltip = attr.ib( default=attr.Factory(Tooltip), validator=instance_of(Tooltip), ) - transparent = attr.ib(default=False, validator=instance_of(bool)) xAxis = attr.ib(default=attr.Factory(XAxis), validator=instance_of(XAxis)) # XXX: This isn't a *good* default, rather it's the default Grafana uses. yAxes = attr.ib( @@ -1135,18 +1192,13 @@ def to_json_data(self): graphObject = { 'aliasColors': self.aliasColors, 'bars': self.bars, - 'datasource': self.dataSource, - 'description': self.description, - 'editable': self.editable, 'error': self.error, 'fill': self.fill, 'grid': self.grid, - 'id': self.id, 'isNew': self.isNew, 'legend': self.legend, 'lines': self.lines, 'linewidth': self.lineWidth, - 'links': self.links, 'minSpan': self.minSpan, 'nullPointMode': self.nullPointMode, 'options': { @@ -1157,24 +1209,17 @@ def to_json_data(self): 'pointradius': self.pointRadius, 'points': self.points, 'renderer': self.renderer, - 'repeat': self.repeat, 'seriesOverrides': self.seriesOverrides, - 'span': self.span, 'stack': self.stack, 'steppedLine': self.steppedLine, - 'targets': self.targets, - 'timeFrom': self.timeFrom, - 'timeShift': self.timeShift, - 'title': self.title, 'tooltip': self.tooltip, - 'transparent': self.transparent, 'type': GRAPH_TYPE, 'xaxis': self.xAxis, 'yaxes': self.yAxes, } if self.alert: graphObject['alert'] = self.alert - return graphObject + return self.panel_json(graphObject) def _iter_targets(self): for target in self.targets: @@ -1277,34 +1322,22 @@ def to_json_data(self): @attr.s -class Text(object): +class Text(Panel): """Generates a Text panel.""" - content = attr.ib() - editable = attr.ib(default=True, validator=instance_of(bool)) + content = attr.ib(default="") error = attr.ib(default=False, validator=instance_of(bool)) - height = attr.ib(default=None) - id = attr.ib(default=None) - links = attr.ib(default=attr.Factory(list)) mode = attr.ib(default=TEXT_MODE_MARKDOWN) - span = attr.ib(default=None) - title = attr.ib(default="") - transparent = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): - return { - 'content': self.content, - 'editable': self.editable, - 'error': self.error, - 'height': self.height, - 'id': self.id, - 'links': self.links, - 'mode': self.mode, - 'span': self.span, - 'title': self.title, - 'transparent': self.transparent, - 'type': TEXT_TYPE, - } + return self.panel_json( + { + 'content': self.content, + 'error': self.error, + 'mode': self.mode, + 'type': TEXT_TYPE, + } + ) @attr.s @@ -1341,7 +1374,7 @@ def to_json_data(self): @attr.s -class Stat(object): +class Stat(Panel): """Generates Stat panel json structure Grafana doc on stat: https://grafana.com/docs/grafana/latest/panels/visualizations/stat-panel/ @@ -1372,71 +1405,52 @@ class Stat(object): :param repeat: defines how the panel should be repeated """ - dataSource = attr.ib() - targets = attr.ib() - title = attr.ib() - description = attr.ib(default=None) colorMode = attr.ib(default='value') graphMode = attr.ib(default='area') orientation = attr.ib(default='auto') alignment = attr.ib(default='auto') - editable = attr.ib(default=True, validator=instance_of(bool)) format = attr.ib(default='none') - height = attr.ib(default=None) - id = attr.ib(default=None) - links = attr.ib(default=attr.Factory(list)) mappings = attr.ib(default=attr.Factory(list)) span = attr.ib(default=6) thresholds = attr.ib(default="") - timeFrom = attr.ib(default=None) - transparent = attr.ib(default=False, validator=instance_of(bool)) reduceCalc = attr.ib(default='mean', type=str) decimals = attr.ib(default=None) repeat = attr.ib(default=attr.Factory(Repeat), validator=instance_of(Repeat)) def to_json_data(self): - return { - 'datasource': self.dataSource, - 'description': self.description, - 'editable': self.editable, - 'id': self.id, - 'links': self.links, - 'height': self.height, - 'fieldConfig': { - 'defaults': { - 'custom': {}, - 'decimals': self.decimals, - 'mappings': self.mappings, - 'thresholds': { - 'mode': 'absolute', - 'steps': self.thresholds, - }, - 'unit': self.format - } - }, - 'options': { - 'colorMode': self.colorMode, - 'graphMode': self.graphMode, - 'justifyMode': self.alignment, - 'orientation': self.orientation, - 'reduceOptions': { - 'calcs': [ - self.reduceCalc - ], - 'fields': '', - 'values': False - } - }, - 'span': self.span, - 'targets': self.targets, - 'title': self.title, - 'transparent': self.transparent, - 'type': STAT_TYPE, - 'timeFrom': self.timeFrom, - 'repeat': self.repeat.variable, - 'repeatDirection': self.repeat.direction, - 'maxPerRow': self.repeat.maxPerRow - } + return self.panel_json( + { + 'fieldConfig': { + 'defaults': { + 'custom': {}, + 'decimals': self.decimals, + 'mappings': self.mappings, + 'thresholds': { + 'mode': 'absolute', + 'steps': self.thresholds, + }, + 'unit': self.format + } + }, + 'options': { + 'colorMode': self.colorMode, + 'graphMode': self.graphMode, + 'justifyMode': self.alignment, + 'orientation': self.orientation, + 'reduceOptions': { + 'calcs': [ + self.reduceCalc + ], + 'fields': '', + 'values': False + } + }, + 'type': STAT_TYPE, + 'repeat': self.repeat.variable, + 'repeatDirection': self.repeat.direction, + 'maxPerRow': self.repeat.maxPerRow + } + ) @attr.s @@ -1512,7 +1526,7 @@ def to_json_data(self): @attr.s -class SingleStat(object): +class SingleStat(Panel): """Generates Single Stat panel json structure This panel was deprecated in Grafana 7.0, please use Stat instead @@ -1565,24 +1579,16 @@ class SingleStat(object): :param timeFrom: time range that Override relative time """ - dataSource = attr.ib() - targets = attr.ib() - title = attr.ib() cacheTimeout = attr.ib(default=None) colors = attr.ib(default=attr.Factory(lambda: [GREEN, ORANGE, RED])) colorBackground = attr.ib(default=False, validator=instance_of(bool)) colorValue = attr.ib(default=False, validator=instance_of(bool)) - description = attr.ib(default=None) decimals = attr.ib(default=None) - editable = attr.ib(default=True, validator=instance_of(bool)) format = attr.ib(default='none') gauge = attr.ib(default=attr.Factory(Gauge), validator=instance_of(Gauge)) - height = attr.ib(default=None) hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) - id = attr.ib(default=None) interval = attr.ib(default=None) - links = attr.ib(default=attr.Factory(list)) mappingType = attr.ib(default=MAPPING_TYPE_VALUE_TO_TEXT) mappingTypes = attr.ib( default=attr.Factory(lambda: [ @@ -1599,60 +1605,46 @@ class SingleStat(object): prefix = attr.ib(default="") prefixFontSize = attr.ib(default='50%') rangeMaps = attr.ib(default=attr.Factory(list)) - repeat = attr.ib(default=None) - span = attr.ib(default=6) sparkline = attr.ib( default=attr.Factory(SparkLine), validator=instance_of(SparkLine), ) thresholds = attr.ib(default="") - transparent = attr.ib(default=False, validator=instance_of(bool)) valueFontSize = attr.ib(default='80%') valueName = attr.ib(default=VTYPE_DEFAULT) valueMaps = attr.ib(default=attr.Factory(list)) - timeFrom = attr.ib(default=None) def to_json_data(self): - return { - 'cacheTimeout': self.cacheTimeout, - 'colorBackground': self.colorBackground, - 'colorValue': self.colorValue, - 'colors': self.colors, - 'datasource': self.dataSource, - 'decimals': self.decimals, - 'description': self.description, - 'editable': self.editable, - 'format': self.format, - 'gauge': self.gauge, - 'id': self.id, - 'interval': self.interval, - 'links': self.links, - 'height': self.height, - 'hideTimeOverride': self.hideTimeOverride, - 'mappingType': self.mappingType, - 'mappingTypes': self.mappingTypes, - 'maxDataPoints': self.maxDataPoints, - 'minSpan': self.minSpan, - 'nullPointMode': self.nullPointMode, - 'nullText': self.nullText, - 'postfix': self.postfix, - 'postfixFontSize': self.postfixFontSize, - 'prefix': self.prefix, - 'prefixFontSize': self.prefixFontSize, - 'rangeMaps': self.rangeMaps, - 'repeat': self.repeat, - 'span': self.span, - 'sparkline': self.sparkline, - 'targets': self.targets, - 'thresholds': self.thresholds, - 'title': self.title, - 'transparent': self.transparent, - 'type': SINGLESTAT_TYPE, - 'valueFontSize': self.valueFontSize, - 'valueMaps': self.valueMaps, - 'valueName': self.valueName, - 'timeFrom': self.timeFrom, - } + return self.panel_json( + { + 'cacheTimeout': self.cacheTimeout, + 'colorBackground': self.colorBackground, + 'colorValue': self.colorValue, + 'colors': self.colors, + 'decimals': self.decimals, + 'format': self.format, + 'gauge': self.gauge, + 'interval': self.interval, + 'hideTimeOverride': self.hideTimeOverride, + 'mappingType': self.mappingType, + 'mappingTypes': self.mappingTypes, + 'maxDataPoints': self.maxDataPoints, + 'minSpan': self.minSpan, + 'nullPointMode': self.nullPointMode, + 'nullText': self.nullText, + 'postfix': self.postfix, + 'postfixFontSize': self.postfixFontSize, + 'prefix': self.prefix, + 'prefixFontSize': self.prefixFontSize, + 'rangeMaps': self.rangeMaps, + 'sparkline': self.sparkline, + 'thresholds': self.thresholds, + 'type': SINGLESTAT_TYPE, + 'valueFontSize': self.valueFontSize, + 'valueMaps': self.valueMaps, + 'valueName': self.valueName, + } + ) @attr.s @@ -1822,7 +1814,7 @@ def _style_columns(columns): @attr.s -class Table(object): +class Table(Panel): """Generates Table panel json structure Grafana doc on table: https://grafana.com/docs/grafana/latest/features/panels/table_panel/#table-panel @@ -1849,30 +1841,19 @@ class Table(object): :param transparent: defines if panel should be transparent """ - dataSource = attr.ib() - targets = attr.ib() - title = attr.ib() columns = attr.ib(default=attr.Factory(list)) - description = attr.ib(default=None) - editable = attr.ib(default=True, validator=instance_of(bool)) fontSize = attr.ib(default='100%') - height = attr.ib(default=None) hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) - id = attr.ib(default=None) - links = attr.ib(default=attr.Factory(list)) minSpan = attr.ib(default=None) pageSize = attr.ib(default=None) - repeat = attr.ib(default=None) scroll = attr.ib(default=True, validator=instance_of(bool)) showHeader = attr.ib(default=True, validator=instance_of(bool)) span = attr.ib(default=6) sort = attr.ib( default=attr.Factory(ColumnSort), validator=instance_of(ColumnSort)) styles = attr.ib() - timeFrom = attr.ib(default=None) transform = attr.ib(default=COLUMNS_TRANSFORM) - transparent = attr.ib(default=False, validator=instance_of(bool)) @styles.default def styles_default(self): @@ -1910,35 +1891,25 @@ def with_styled_columns(cls, columns, styles=None, **kwargs): return cls(columns=columns, styles=styles + extraStyles, **kwargs) def to_json_data(self): - return { - 'columns': self.columns, - 'datasource': self.dataSource, - 'description': self.description, - 'editable': self.editable, - 'fontSize': self.fontSize, - 'height': self.height, - 'hideTimeOverride': self.hideTimeOverride, - 'id': self.id, - 'links': self.links, - 'minSpan': self.minSpan, - 'pageSize': self.pageSize, - 'repeat': self.repeat, - 'scroll': self.scroll, - 'showHeader': self.showHeader, - 'span': self.span, - 'sort': self.sort, - 'styles': self.styles, - 'targets': self.targets, - 'timeFrom': self.timeFrom, - 'title': self.title, - 'transform': self.transform, - 'transparent': self.transparent, - 'type': TABLE_TYPE, - } + return self.panel_json( + { + 'columns': self.columns, + 'fontSize': self.fontSize, + 'hideTimeOverride': self.hideTimeOverride, + 'minSpan': self.minSpan, + 'pageSize': self.pageSize, + 'scroll': self.scroll, + 'showHeader': self.showHeader, + 'sort': self.sort, + 'styles': self.styles, + 'transform': self.transform, + 'type': TABLE_TYPE, + } + ) @attr.s -class BarGauge(object): +class BarGauge(Panel): """Generates Bar Gauge panel json structure :param allValue: If All values should be shown or a Calculation @@ -1955,7 +1926,7 @@ class BarGauge(object): :param hideTimeOverride: hides time overrides :param id: panel id :param interval: defines time interval between metric queries - :param labels: oprion to show gauge level labels + :param labels: option to show gauge level labels :param limit: limit of number of values to show when not Calculating :param links: additional web links :param max: maximum value of the gauge @@ -1977,15 +1948,11 @@ class BarGauge(object): :param valueMaps: the list of value to text mappings """ - title = attr.ib() - targets = attr.ib() allValues = attr.ib(default=False, validator=instance_of(bool)) cacheTimeout = attr.ib(default=None) calc = attr.ib(default=GAUGE_CALC_MEAN) dataLinks = attr.ib(default=attr.Factory(list)) - dataSource = attr.ib(default=None) decimals = attr.ib(default=None) - description = attr.ib(default=None) displayMode = attr.ib( default=GAUGE_DISPLAY_MODE_LCD, validator=in_( @@ -1996,15 +1963,11 @@ class BarGauge(object): ] ), ) - editable = attr.ib(default=True, validator=instance_of(bool)) format = attr.ib(default='none') - height = attr.ib(default=None) hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) - id = attr.ib(default=None) interval = attr.ib(default=None) label = attr.ib(default=None) limit = attr.ib(default=None) - links = attr.ib(default=attr.Factory(list)) max = attr.ib(default=100) maxDataPoints = attr.ib(default=100) min = attr.ib(default=0) @@ -2014,8 +1977,6 @@ class BarGauge(object): validator=in_([ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL]), ) rangeMaps = attr.ib(default=attr.Factory(list)) - repeat = attr.ib(default=None) - span = attr.ib(default=6) thresholdLabels = attr.ib(default=False, validator=instance_of(bool)) thresholdMarkers = attr.ib(default=True, validator=instance_of(bool)) thresholds = attr.ib( @@ -2027,59 +1988,45 @@ class BarGauge(object): ), validator=instance_of(list), ) - timeFrom = attr.ib(default=None) - timeShift = attr.ib(default=None) - transparent = attr.ib(default=False, validator=instance_of(bool)) valueMaps = attr.ib(default=attr.Factory(list)) def to_json_data(self): - return { - 'cacheTimeout': self.cacheTimeout, - 'datasource': self.dataSource, - 'description': self.description, - 'editable': self.editable, - 'height': self.height, - 'hideTimeOverride': self.hideTimeOverride, - 'id': self.id, - 'interval': self.interval, - 'links': self.links, - 'maxDataPoints': self.maxDataPoints, - 'minSpan': self.minSpan, - 'options': { - 'displayMode': self.displayMode, - 'fieldOptions': { - 'calcs': [self.calc], - 'defaults': { - 'decimals': self.decimals, - 'max': self.max, - 'min': self.min, - 'title': self.label, - 'unit': self.format, - 'links': self.dataLinks, + return self.panel_json( + { + 'cacheTimeout': self.cacheTimeout, + 'hideTimeOverride': self.hideTimeOverride, + 'interval': self.interval, + 'maxDataPoints': self.maxDataPoints, + 'minSpan': self.minSpan, + 'options': { + 'displayMode': self.displayMode, + 'fieldOptions': { + 'calcs': [self.calc], + 'defaults': { + 'decimals': self.decimals, + 'max': self.max, + 'min': self.min, + 'title': self.label, + 'unit': self.format, + 'links': self.dataLinks, + }, + 'limit': self.limit, + 'mappings': self.valueMaps, + 'override': {}, + 'thresholds': self.thresholds, + 'values': self.allValues, }, - 'limit': self.limit, - 'mappings': self.valueMaps, - 'override': {}, - 'thresholds': self.thresholds, - 'values': self.allValues, + 'orientation': self.orientation, + 'showThresholdLabels': self.thresholdLabels, + 'showThresholdMarkers': self.thresholdMarkers, }, - 'orientation': self.orientation, - 'showThresholdLabels': self.thresholdLabels, - 'showThresholdMarkers': self.thresholdMarkers, - }, - 'repeat': self.repeat, - 'span': self.span, - 'targets': self.targets, - 'timeFrom': self.timeFrom, - 'timeShift': self.timeShift, - 'title': self.title, - 'transparent': self.transparent, - 'type': BARGAUGE_TYPE, - } + 'type': BARGAUGE_TYPE, + } + ) @attr.s -class GaugePanel(object): +class GaugePanel(Panel): """Generates Gauge panel json structure :param allValue: If All values should be shown or a Calculation @@ -2116,31 +2063,21 @@ class GaugePanel(object): :param valueMaps: the list of value to text mappings """ - title = attr.ib() - targets = attr.ib() allValues = attr.ib(default=False, validator=instance_of(bool)) cacheTimeout = attr.ib(default=None) calc = attr.ib(default=GAUGE_CALC_MEAN) dataLinks = attr.ib(default=attr.Factory(list)) - dataSource = attr.ib(default=None) decimals = attr.ib(default=None) - description = attr.ib(default=None) - editable = attr.ib(default=True, validator=instance_of(bool)) format = attr.ib(default='none') - height = attr.ib(default=None) hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) - id = attr.ib(default=None) interval = attr.ib(default=None) label = attr.ib(default=None) limit = attr.ib(default=None) - links = attr.ib(default=attr.Factory(list)) max = attr.ib(default=100) maxDataPoints = attr.ib(default=100) min = attr.ib(default=0) minSpan = attr.ib(default=None) rangeMaps = attr.ib(default=attr.Factory(list)) - repeat = attr.ib(default=None) - span = attr.ib(default=6) thresholdLabels = attr.ib(default=False, validator=instance_of(bool)) thresholdMarkers = attr.ib(default=True, validator=instance_of(bool)) thresholds = attr.ib( @@ -2152,53 +2089,39 @@ class GaugePanel(object): ), validator=instance_of(list), ) - timeFrom = attr.ib(default=None) - timeShift = attr.ib(default=None) - transparent = attr.ib(default=False, validator=instance_of(bool)) valueMaps = attr.ib(default=attr.Factory(list)) def to_json_data(self): - return { - 'cacheTimeout': self.cacheTimeout, - 'datasource': self.dataSource, - 'description': self.description, - 'editable': self.editable, - 'height': self.height, - 'hideTimeOverride': self.hideTimeOverride, - 'id': self.id, - 'interval': self.interval, - 'links': self.links, - 'maxDataPoints': self.maxDataPoints, - 'minSpan': self.minSpan, - 'options': { - 'fieldOptions': { - 'calcs': [self.calc], - 'defaults': { - 'decimals': self.decimals, - 'max': self.max, - 'min': self.min, - 'title': self.label, - 'unit': self.format, - 'links': self.dataLinks, + return self.panel_json( + { + 'cacheTimeout': self.cacheTimeout, + 'hideTimeOverride': self.hideTimeOverride, + 'interval': self.interval, + 'maxDataPoints': self.maxDataPoints, + 'minSpan': self.minSpan, + 'options': { + 'fieldOptions': { + 'calcs': [self.calc], + 'defaults': { + 'decimals': self.decimals, + 'max': self.max, + 'min': self.min, + 'title': self.label, + 'unit': self.format, + 'links': self.dataLinks, + }, + 'limit': self.limit, + 'mappings': self.valueMaps, + 'override': {}, + 'thresholds': self.thresholds, + 'values': self.allValues, }, - 'limit': self.limit, - 'mappings': self.valueMaps, - 'override': {}, - 'thresholds': self.thresholds, - 'values': self.allValues, + 'showThresholdLabels': self.thresholdLabels, + 'showThresholdMarkers': self.thresholdMarkers, }, - 'showThresholdLabels': self.thresholdLabels, - 'showThresholdMarkers': self.thresholdMarkers, - }, - 'repeat': self.repeat, - 'span': self.span, - 'targets': self.targets, - 'timeFrom': self.timeFrom, - 'timeShift': self.timeShift, - 'title': self.title, - 'transparent': self.transparent, - 'type': GAUGE_TYPE, - } + 'type': GAUGE_TYPE, + } + ) @attr.s @@ -2236,7 +2159,7 @@ def to_json_data(self): @attr.s -class Heatmap(object): +class Heatmap(Panel): """Generates Heatmap panel json structure (https://grafana.com/docs/grafana/latest/features/panels/heatmap/) :param heatmap @@ -2254,19 +2177,12 @@ class Heatmap(object): :param transparent: defines if the panel should be transparent """ - title = attr.ib() - description = attr.ib(default=None) - id = attr.ib(default=None) # The below does not really like the Legend class we have defined above legend = attr.ib(default={'show': False}) - links = attr.ib(default=None) - targets = attr.ib(default=None) tooltip = attr.ib( default=attr.Factory(Tooltip), validator=instance_of(Tooltip), ) - span = attr.ib(default=None) - cards = attr.ib( default={ 'cardPadding': None, @@ -2280,12 +2196,10 @@ class Heatmap(object): ) dataFormat = attr.ib(default='timeseries') - datasource = attr.ib(default=None) heatmap = {} hideZeroBuckets = attr.ib(default=False) highlightCards = attr.ib(default=True) options = attr.ib(default=None) - transparent = attr.ib(default=False, validator=instance_of(bool)) xAxis = attr.ib( default=attr.Factory(XAxis), @@ -2304,34 +2218,28 @@ class Heatmap(object): reverseYBuckets = attr.ib(default=False) def to_json_data(self): - return { - 'cards': self.cards, - 'color': self.color, - 'dataFormat': self.dataFormat, - 'datasource': self.datasource, - 'description': self.description, - 'heatmap': self.heatmap, - 'hideZeroBuckets': self.hideZeroBuckets, - 'highlightCards': self.highlightCards, - 'id': self.id, - 'legend': self.legend, - 'links': self.links, - 'options': self.options, - 'reverseYBuckets': self.reverseYBuckets, - 'span': self.span, - 'targets': self.targets, - 'title': self.title, - 'tooltip': self.tooltip, - 'transparent': self.transparent, - 'type': HEATMAP_TYPE, - 'xAxis': self.xAxis, - 'xBucketNumber': self.xBucketNumber, - 'xBucketSize': self.xBucketSize, - 'yAxis': self.yAxis, - 'yBucketBound': self.yBucketBound, - 'yBucketNumber': self.yBucketNumber, - 'yBucketSize': self.yBucketSize - } + return self.panel_json( + { + 'cards': self.cards, + 'color': self.color, + 'dataFormat': self.dataFormat, + 'heatmap': self.heatmap, + 'hideZeroBuckets': self.hideZeroBuckets, + 'highlightCards': self.highlightCards, + 'legend': self.legend, + 'options': self.options, + 'reverseYBuckets': self.reverseYBuckets, + 'tooltip': self.tooltip, + 'type': HEATMAP_TYPE, + 'xAxis': self.xAxis, + 'xBucketNumber': self.xBucketNumber, + 'xBucketSize': self.xBucketSize, + 'yAxis': self.yAxis, + 'yBucketBound': self.yBucketBound, + 'yBucketNumber': self.yBucketNumber, + 'yBucketSize': self.yBucketSize + } + ) @attr.s @@ -2372,7 +2280,7 @@ def to_json_data(self): @attr.s -class Statusmap(object): +class Statusmap(Panel): """Generates json structure for the flant-statusmap-panel visualisation plugin (https://grafana.com/grafana/plugins/flant-statusmap-panel/). :param alert @@ -2398,9 +2306,6 @@ class Statusmap(object): :param yAxis """ - targets = attr.ib() - title = attr.ib() - alert = attr.ib(default=None) cards = attr.ib( default={ @@ -2414,27 +2319,18 @@ class Statusmap(object): default=attr.Factory(StatusmapColor), validator=instance_of(StatusmapColor), ) - dataSource = attr.ib(default=None) - description = attr.ib(default=None) - editable = attr.ib(default=True, validator=instance_of(bool)) - id = attr.ib(default=None) isNew = attr.ib(default=True, validator=instance_of(bool)) legend = attr.ib( default=attr.Factory(Legend), validator=instance_of(Legend), ) - links = attr.ib(default=attr.Factory(list)) minSpan = attr.ib(default=None) nullPointMode = attr.ib(default=NULL_AS_ZERO) - span = attr.ib(default=None) - timeFrom = attr.ib(default=None) - timeShift = attr.ib(default=None) tooltip = attr.ib( default=attr.Factory(Tooltip), validator=instance_of(Tooltip), ) - transparent = attr.ib(default=False, validator=instance_of(bool)) xAxis = attr.ib( default=attr.Factory(XAxis), validator=instance_of(XAxis) @@ -2446,34 +2342,23 @@ class Statusmap(object): def to_json_data(self): graphObject = { - 'datasource': self.dataSource, - 'description': self.description, - 'editable': self.editable, 'color': self.color, - 'id': self.id, 'isNew': self.isNew, 'legend': self.legend, - 'links': self.links, 'minSpan': self.minSpan, 'nullPointMode': self.nullPointMode, - 'span': self.span, - 'targets': self.targets, - 'timeFrom': self.timeFrom, - 'timeShift': self.timeShift, - 'title': self.title, 'tooltip': self.tooltip, - 'transparent': self.transparent, 'type': STATUSMAP_TYPE, 'xaxis': self.xAxis, 'yaxis': self.yAxis, } if self.alert: graphObject['alert'] = self.alert - return graphObject + return self.panel_json(graphObject) @attr.s -class Svg(object): +class Svg(Panel): """Generates SVG panel json structure Grafana doc on SVG: https://grafana.com/grafana/plugins/marcuscalidus-svg-panel :param dataSource: Grafana datasource name @@ -2493,18 +2378,10 @@ class Svg(object): :param svgFilePath: path to SVG image file to be displayed """ - dataSource = attr.ib() - targets = attr.ib() - title = attr.ib() - description = attr.ib(default=None) - editable = attr.ib(default=True, validator=instance_of(bool)) format = attr.ib(default='none') jsCodeFilePath = attr.ib(default="", validator=instance_of(str)) jsCodeInitFilePath = attr.ib(default="", validator=instance_of(str)) height = attr.ib(default=None) - id = attr.ib(default=None) - links = attr.ib(default=attr.Factory(list)) - span = attr.ib(default=6) svgFilePath = attr.ib(default="", validator=instance_of(str)) @staticmethod @@ -2522,27 +2399,20 @@ def to_json_data(self): js_init_code = self.read_file(self.jsCodeInitFilePath) svg_data = self.read_file(self.svgFilePath) - return { - 'datasource': self.dataSource, - 'description': self.description, - 'editable': self.editable, - 'id': self.id, - 'links': self.links, - 'height': self.height, - 'format': self.format, - 'js_code': js_code, - 'js_init_code': js_init_code, - 'span': self.span, - 'svg_data': svg_data, - 'targets': self.targets, - 'title': self.title, - 'type': SVG_TYPE, - 'useSVGBuilder': False - } + return self.panel_json( + { + 'format': self.format, + 'js_code': js_code, + 'js_init_code': js_init_code, + 'svg_data': svg_data, + 'type': SVG_TYPE, + 'useSVGBuilder': False + } + ) @attr.s -class PieChart(object): +class PieChart(Panel): """Generates Pie Chart panel json structure Grafana doc on Pie Chart: https://grafana.com/grafana/plugins/grafana-piechart-panel :param dataSource: Grafana datasource name @@ -2562,52 +2432,33 @@ class PieChart(object): :param transparent: defines if the panel is transparent """ - dataSource = attr.ib() - targets = attr.ib() - title = attr.ib() - description = attr.ib(default=None) - editable = attr.ib(default=True, validator=instance_of(bool)) format = attr.ib(default='none') - height = attr.ib(default=None) - id = attr.ib(default=None) - links = attr.ib(default=attr.Factory(list)) legendType = attr.ib(default='Right side') pieType = attr.ib(default='pie') showLegend = attr.ib(default=True) showLegendValues = attr.ib(default=True) - span = attr.ib(default=6) thresholds = attr.ib(default="") - timeFrom = attr.ib(default=None) - transparent = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): - return { - 'datasource': self.dataSource, - 'description': self.description, - 'editable': self.editable, - 'format': self.format, - 'id': self.id, - 'links': self.links, - 'pieType': self.pieType, - 'height': self.height, - 'fieldConfig': { - 'defaults': { - 'custom': {}, + return self.panel_json( + { + 'format': self.format, + 'pieType': self.pieType, + 'height': self.height, + 'fieldConfig': { + 'defaults': { + 'custom': {}, + }, + 'overrides': [] }, - 'overrides': [] - }, - 'legend': { - 'show': self.showLegend, - 'values': self.showLegendValues - }, - 'legendType': self.legendType, - 'span': self.span, - 'targets': self.targets, - 'title': self.title, - 'type': PIE_CHART_TYPE, - 'timeFrom': self.timeFrom, - 'transparent': self.transparent - } + 'legend': { + 'show': self.showLegend, + 'values': self.showLegendValues + }, + 'legendType': self.legendType, + 'type': PIE_CHART_TYPE, + } + ) @attr.s From 3827f69c52cc748e3502fc5fcab165b584e86b15 Mon Sep 17 00:00:00 2001 From: shi-ron <74239685+shi-ron@users.noreply.github.com> Date: Wed, 16 Dec 2020 15:15:43 +0100 Subject: [PATCH 173/403] Add missing textMode in Stat Co-authored-by: JamesGibo --- CHANGELOG.rst | 2 ++ grafanalib/core.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5f58df66..2dbfa630 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,10 @@ Changelog * Added Alert Threshold enabled/disabled to Graphs. * Added constants for all Grafana value formats * Added support for repetitions to Stat Panels (https://grafana.com/docs/grafana/latest/variables/repeat-panels-or-rows/) +* Added textMode option to Stat Panels * Add Panel object for all panels to inherit from + Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index bbd2e411..5909e5ac 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1382,6 +1382,7 @@ class Stat(Panel): :param dataSource: Grafana datasource name :param targets: list of metric requests for chosen datasource :param title: panel title + :param textMode: define Grafana will show name or value: keys: 'auto' 'name' 'none' 'value' 'value_and_name' :param colorMode: defines if Grafana will color panel background: keys "value" "background" :param graphMode: defines if Grafana will draw graph: keys 'area' 'none' :param orientation: Stacking direction in case of multiple series or fields: keys 'auto' 'horizontal' 'vertical' @@ -1405,6 +1406,7 @@ class Stat(Panel): :param repeat: defines how the panel should be repeated """ + textMode = attr.ib(default='auto') colorMode = attr.ib(default='value') graphMode = attr.ib(default='area') orientation = attr.ib(default='auto') @@ -1433,6 +1435,7 @@ def to_json_data(self): } }, 'options': { + 'textMode': self.textMode, 'colorMode': self.colorMode, 'graphMode': self.graphMode, 'justifyMode': self.alignment, From 71f2b41dcea42763f168b169bddcf2a88e93a444 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Fri, 18 Dec 2020 14:40:10 +0000 Subject: [PATCH 174/403] Add support for dashboard list panel --- CHANGELOG.rst | 1 + grafanalib/core.py | 52 +++++++++++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 10 +++++++ 3 files changed, 63 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2dbfa630..8b6044f5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ Changelog * Added support for repetitions to Stat Panels (https://grafana.com/docs/grafana/latest/variables/repeat-panels-or-rows/) * Added textMode option to Stat Panels * Add Panel object for all panels to inherit from +* Add Dashboard list panel (https://grafana.com/docs/grafana/latest/panels/visualizations/dashboard-list-panel/) Changes diff --git a/grafanalib/core.py b/grafanalib/core.py index 5909e5ac..ee3922be 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -82,6 +82,7 @@ def to_json_data(self): ALERTLIST_TYPE = 'alertlist' BARGAUGE_TYPE = 'bargauge' GAUGE_TYPE = 'gauge' +DASHBOARDLIST_TYPE = 'dashlist' HEATMAP_TYPE = 'heatmap' STATUSMAP_TYPE = 'flant-statusmap-panel' SVG_TYPE = 'marcuscalidus-svg-panel' @@ -2464,6 +2465,57 @@ def to_json_data(self): ) +@attr.s +class DashboardList(Panel): + """Generates Dashboard list panel json structure + Grafana doc on Dashboard list: https://grafana.com/docs/grafana/latest/panels/visualizations/dashboard-list-panel/ + :param title: panel title + :param description: optional panel description + :param editable: defines if panel is editable via web interfaces + :param height: defines panel height + :param id: panel id + :param links: additional web links + :param span: defines the number of spans that will be used for panel + :param transparent: defines if the panel is transparent + + :param showHeadings: The chosen list selection (Starred, Recently viewed, Search) is shown as a heading + :param showSearch: Display dashboards by search query or tags. Requires you to enter at least one value in Query or Tags + :param showRecent: Display recently viewed dashboards in alphabetical order + :param showStarred: Display starred dashboards in alphabetical order + :param maxItems: Sets the maximum number of items to list per section + :param searchQuery: Enter the query you want to search by + :param searchTags: List of tags you want to search by + """ + showHeadings = attr.ib(default=True) + showSearch = attr.ib(default=False) + showRecent = attr.ib(default=False) + showStarred = attr.ib(default=True) + maxItems = attr.ib(default=10, validator=instance_of(int)) + searchQuery = attr.ib(default='', validator=instance_of(str)) + searchTags = attr.ib(default=attr.Factory(list), validator=instance_of(list)) + + def to_json_data(self): + return self.panel_json( + { + 'height': self.height, + 'fieldConfig': { + 'defaults': { + 'custom': {}, + }, + 'overrides': [] + }, + 'headings': self.showHeadings, + 'search': self.showSearch, + 'recent': self.showRecent, + 'starred': self.showStarred, + 'limit': self.maxItems, + 'query': self.searchQuery, + 'tags': self.searchTags, + 'type': DASHBOARDLIST_TYPE, + } + ) + + @attr.s class Threshold(object): """Threshold for a gauge diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 2d8f571e..d85e3da0 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -121,3 +121,13 @@ def test_single_stat(): assert data['targets'] == targets assert data['datasource'] == data_source assert data['title'] == title + + +def test_dashboard_list(): + title = 'dummy title' + dashboard_list = G.DashboardList(title=title) + data = dashboard_list.to_json_data() + assert data['targets'] == [] + assert data['datasource'] is None + assert data['title'] == title + assert data['starred'] is True From 05c46b08c09ecb7f1e3d0f5f5f70b45056cf8dd9 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Fri, 18 Dec 2020 14:51:00 +0000 Subject: [PATCH 175/403] Add validator to dashboard list arguments (#294) --- grafanalib/core.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index ee3922be..e9996fef 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2486,10 +2486,10 @@ class DashboardList(Panel): :param searchQuery: Enter the query you want to search by :param searchTags: List of tags you want to search by """ - showHeadings = attr.ib(default=True) - showSearch = attr.ib(default=False) - showRecent = attr.ib(default=False) - showStarred = attr.ib(default=True) + showHeadings = attr.ib(default=True, validator=instance_of(bool)) + showSearch = attr.ib(default=False, validator=instance_of(bool)) + showRecent = attr.ib(default=False, validator=instance_of(bool)) + showStarred = attr.ib(default=True, validator=instance_of(bool)) maxItems = attr.ib(default=10, validator=instance_of(int)) searchQuery = attr.ib(default='', validator=instance_of(str)) searchTags = attr.ib(default=attr.Factory(list), validator=instance_of(list)) From c56659471cf9ffc9f89ba59a4de22607cd165aef Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 18 Dec 2020 16:09:37 +0100 Subject: [PATCH 176/403] Prepare 0.5.9 release (#295) * Prepare 0.5.9 release fixes #292 * add missing bullet points --- CHANGELOG.rst | 7 +++++-- setup.py | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8b6044f5..3405e2cd 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,8 +2,8 @@ Changelog ========= -0.5.9 (TBD) -=========== +0.5.9 (2020-12-18) +================== * Added Alert Threshold enabled/disabled to Graphs. * Added constants for all Grafana value formats @@ -18,6 +18,9 @@ Changes * Change supported python versions from 3.6 to 3.9 * Added hide parameter to Target +* Updated dependencies (docs, build, CI) +* Consistent coding style + 0.5.8 (2020-11-02) ================== diff --git a/setup.py b/setup.py index 2a3216b6..32f784ab 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.8', + version='0.5.9', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 152ffd7736438147d65b767e2701f73eed553991 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Sat, 19 Dec 2020 10:46:44 +0100 Subject: [PATCH 177/403] add changelog for new release (#296) --- CHANGELOG.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3405e2cd..9a39d3ca 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,18 @@ Changelog ========= +0.x.x (TBD) +=========== + +* ... + +Changes +------- + +* ... + + + 0.5.9 (2020-12-18) ================== From f5a44b1fea1bc3caa5ebf0bdf6272a7bf5fd2cb5 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Sat, 19 Dec 2020 10:13:13 +0000 Subject: [PATCH 178/403] Add logs panel --- CHANGELOG.rst | 1 + grafanalib/core.py | 45 ++++++++++++++++++++++++++++++++++- grafanalib/tests/test_core.py | 15 ++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9a39d3ca..808dfe3e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ Changelog 0.x.x (TBD) =========== +* Added Logs panel (https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/) * ... Changes diff --git a/grafanalib/core.py b/grafanalib/core.py index e9996fef..4957936f 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -83,6 +83,7 @@ def to_json_data(self): BARGAUGE_TYPE = 'bargauge' GAUGE_TYPE = 'gauge' DASHBOARDLIST_TYPE = 'dashlist' +LOGS_TYPE = 'logs' HEATMAP_TYPE = 'heatmap' STATUSMAP_TYPE = 'flant-statusmap-panel' SVG_TYPE = 'marcuscalidus-svg-panel' @@ -2497,7 +2498,6 @@ class DashboardList(Panel): def to_json_data(self): return self.panel_json( { - 'height': self.height, 'fieldConfig': { 'defaults': { 'custom': {}, @@ -2516,6 +2516,49 @@ def to_json_data(self): ) +@attr.s +class Logs(Panel): + """Generates Logs panel json structure + Grafana doc on Logs panel: https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/ + :param title: panel title + :param description: optional panel description + :param editable: defines if panel is editable via web interfaces + :param height: defines panel height + :param id: panel id + :param links: additional web links + :param span: defines the number of spans that will be used for panel + :param transparent: defines if the panel is transparent + + :param showLabels: Show or hide the unique labels column, which shows only non-common labels + :param showTime: Show or hide the log timestamp column + :param wrapLogMessages: Toggle line wrapping + :param sortOrder: Display results in 'Descending' or 'Ascending' time order. The default is Descending, showing the newest logs first. + """ + showLabels = attr.ib(default=False, validator=instance_of(bool)) + showTime = attr.ib(default=False, validator=instance_of(bool)) + wrapLogMessages = attr.ib(default=False, validator=instance_of(bool)) + sortOrder = attr.ib(default='Descending', validator=instance_of(str)) + + def to_json_data(self): + return self.panel_json( + { + 'fieldConfig': { + 'defaults': { + 'custom': {}, + }, + 'overrides': [] + }, + 'options': { + 'showLabels': self.showLabels, + 'showTime': self.showTime, + 'sortOrder': self.sortOrder, + 'wrapLogMessage': self.wrapLogMessages + }, + 'type': LOGS_TYPE, + } + ) + + @attr.s class Threshold(object): """Threshold for a gauge diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index d85e3da0..cc8225b1 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -131,3 +131,18 @@ def test_dashboard_list(): assert data['datasource'] is None assert data['title'] == title assert data['starred'] is True + + +def test_logs_panel(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + logs = G.Logs(data_source, targets, title) + data = logs.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title + assert data['options']['showLabels'] is False + assert data['options']['showTime'] is False + assert data['options']['wrapLogMessage'] is False + assert data['options']['sortOrder'] == 'Descending' From eb1f9c3db30c2c7640d89625a4cbb4f60235e35f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 07:46:49 +0000 Subject: [PATCH 179/403] Bump actions/setup-python from v2.1.4 to v2.2.1 (#298) Bumps [actions/setup-python](https://github.com/actions/setup-python) from v2.1.4 to v2.2.1. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.1.4...3105fb18c05ddd93efea5f9e0bef7a03a6e9e7df) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 6e91d590..fcb85c9a 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.3.4 - name: Set up Python - uses: actions/setup-python@v2.1.4 + uses: actions/setup-python@v2.2.1 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index f23e63c0..bcdf3528 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.3.4 - name: Set up Python 3.7 - uses: actions/setup-python@v2.1.4 + uses: actions/setup-python@v2.2.1 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 8a82e49e..93d471f4 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2.3.4 - name: Set up Python - uses: actions/setup-python@v2.1.4 + uses: actions/setup-python@v2.2.1 with: python-version: ${{ matrix.python }} - name: Run tests From 7ac8f5b349a41ca94f6c849d116894f8efabcf8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Dec 2020 07:47:25 +0000 Subject: [PATCH 180/403] Bump sphinx from 3.3.1 to 3.4.0 (#299) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.3.1 to 3.4.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.3.1...v3.4.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index f7531db7..269e3469 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.3.1 +sphinx == 3.4.0 sphinx_rtd_theme == 0.5.0 From beac4f9a2220ba243b6a157ca6aa52965f924c12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Jan 2021 08:08:38 +0000 Subject: [PATCH 181/403] Bump sphinx from 3.4.0 to 3.4.1 (#300) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.4.0 to 3.4.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.4.0...v3.4.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 269e3469..edf3843f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.4.0 +sphinx == 3.4.1 sphinx_rtd_theme == 0.5.0 From 3a6d5f233f2520040088520c1ba4b42ceff9d7ad Mon Sep 17 00:00:00 2001 From: Puneeth Nanjundaswamy Date: Tue, 5 Jan 2021 16:16:36 +0000 Subject: [PATCH 182/403] Add Cloudwatch Target --- grafanalib/cloudwatch.py | 57 +++++++++++++++++++++++++++++ grafanalib/tests/test_cloudwatch.py | 30 +++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 grafanalib/cloudwatch.py create mode 100644 grafanalib/tests/test_cloudwatch.py diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py new file mode 100644 index 00000000..2f718593 --- /dev/null +++ b/grafanalib/cloudwatch.py @@ -0,0 +1,57 @@ +"""Helpers to create Cloudwatch-specific Grafana queries.""" + +import attr + +from attr.validators import instance_of + + +@attr.s +class CloudwatchTarget(object): + """ + Generates Cloudwatch target JSON structure. + + Grafana docs on using Cloudwatch: + https://grafana.com/docs/features/datasources/cloudwatch/ + AWS docs on Cloudwatch metrics: + https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html + https://grafana.com/docs/grafana/latest/datasources/cloudwatch/#metric-math-expressions + + :param alias: legend alias + :param dimensions: Cloudwatch dimensions dict + :param expression: Cloudwatch Metric math expressions + :param id: unique id + :param matchExact: Only show metrics that exactly match all defined dimension names. + :param metricName: Cloudwatch metric name + :param namespace: Cloudwatch namespace + :param period: Cloudwatch data period + :param refId: target reference id + :param region: Cloudwatch region + :param statistics: Cloudwatch mathematic statistic + """ + alias = attr.ib(default="") + dimensions = attr.ib(default={}, validator=instance_of(dict)) + expression = attr.ib(default="") + id = attr.ib(default="") + matchExact = attr.ib(default=True, validator=instance_of(bool)) + metricName = attr.ib(default="") + namespace = attr.ib(default="") + period = attr.ib(default="") + refId = attr.ib(default="") + region = attr.ib(default="default") + statistics = attr.ib(default=["Average"], validator=instance_of(list)) + + def to_json_data(self): + + return { + "alias": self.alias, + "dimensions": self.dimensions, + "expression": self.expression, + "id": self.id, + "matchExact": self.matchExact, + "metricName": self.metricName, + "namespace": self.namespace, + "period": self.period, + "refId": self.refId, + "region": self.region, + "statistics": self.statistics + } diff --git a/grafanalib/tests/test_cloudwatch.py b/grafanalib/tests/test_cloudwatch.py new file mode 100644 index 00000000..9179204b --- /dev/null +++ b/grafanalib/tests/test_cloudwatch.py @@ -0,0 +1,30 @@ +"""Tests for Cloudwatch Datasource""" + +import grafanalib.core as G +import grafanalib.cloudwatch as C +from grafanalib import _gen + +import sys +if sys.version_info[0] < 3: + from io import BytesIO as StringIO +else: + from io import StringIO + + +def test_serialization_cloudwatch_target(): + """Serializing a graph doesn't explode.""" + graph = G.Graph( + title="Lambda Duration", + dataSource="Cloudwatch data source", + targets=[ + C.CloudwatchTarget(), + ], + id=1, + yAxes=G.YAxes( + G.YAxis(format=G.SHORT_FORMAT, label="ms"), + G.YAxis(format=G.SHORT_FORMAT), + ), + ) + stream = StringIO() + _gen.write_dashboard(graph, stream) + assert stream.getvalue() != '' From 0eb5525c262e7ffbe841ae5f13c511ca61ef15c4 Mon Sep 17 00:00:00 2001 From: Puneeth Nanjundaswamy Date: Tue, 5 Jan 2021 16:18:05 +0000 Subject: [PATCH 183/403] rename Cloudwatch Target --- grafanalib/cloudwatch.py | 2 +- grafanalib/tests/test_cloudwatch.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py index 2f718593..550c35a4 100644 --- a/grafanalib/cloudwatch.py +++ b/grafanalib/cloudwatch.py @@ -6,7 +6,7 @@ @attr.s -class CloudwatchTarget(object): +class CloudwatchMetricsTarget(object): """ Generates Cloudwatch target JSON structure. diff --git a/grafanalib/tests/test_cloudwatch.py b/grafanalib/tests/test_cloudwatch.py index 9179204b..f31b57e4 100644 --- a/grafanalib/tests/test_cloudwatch.py +++ b/grafanalib/tests/test_cloudwatch.py @@ -11,13 +11,13 @@ from io import StringIO -def test_serialization_cloudwatch_target(): +def test_serialization_cloudwatch_metrics_target(): """Serializing a graph doesn't explode.""" graph = G.Graph( title="Lambda Duration", dataSource="Cloudwatch data source", targets=[ - C.CloudwatchTarget(), + C.CloudwatchMetricsTarget(), ], id=1, yAxes=G.YAxes( From 2abbd0e6d6d010a8c62052890add1ce990cf7f5f Mon Sep 17 00:00:00 2001 From: Puneeth Nanjundaswamy Date: Tue, 5 Jan 2021 16:22:50 +0000 Subject: [PATCH 184/403] links --- grafanalib/cloudwatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py index 550c35a4..6ec6d16c 100644 --- a/grafanalib/cloudwatch.py +++ b/grafanalib/cloudwatch.py @@ -12,9 +12,9 @@ class CloudwatchMetricsTarget(object): Grafana docs on using Cloudwatch: https://grafana.com/docs/features/datasources/cloudwatch/ + AWS docs on Cloudwatch metrics: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html - https://grafana.com/docs/grafana/latest/datasources/cloudwatch/#metric-math-expressions :param alias: legend alias :param dimensions: Cloudwatch dimensions dict From 78cf7d00ca35d6be132e3633253648e69123b581 Mon Sep 17 00:00:00 2001 From: Puneeth Nanjundaswamy Date: Tue, 5 Jan 2021 16:32:52 +0000 Subject: [PATCH 185/403] switch to lychee --- .github/workflows/check-sphinx-and-links.yml | 4 ++-- docs/api/grafanalib.rst | 25 +++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index fcb85c9a..a4610ca0 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,8 +27,8 @@ jobs: - name: Link Checker id: lc - uses: peter-evans/link-checker@v1 + uses: lycheeverse/lychee-action@v1 with: - args: -x 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' -r docs/build/html/* + args: --exclude 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' -r docs/build/html/* - name: Fail if there were link errors run: exit ${{ steps.lc.outputs.exit_code }} diff --git a/docs/api/grafanalib.rst b/docs/api/grafanalib.rst index 4638337a..7ac152a6 100644 --- a/docs/api/grafanalib.rst +++ b/docs/api/grafanalib.rst @@ -4,6 +4,14 @@ grafanalib package Submodules ---------- +grafanalib.cloudwatch module +---------------------------- + +.. automodule:: grafanalib.cloudwatch + :members: + :undoc-members: + :show-inheritance: + grafanalib.core module ---------------------- @@ -20,6 +28,22 @@ grafanalib.elasticsearch module :undoc-members: :show-inheritance: +grafanalib.formatunits module +----------------------------- + +.. automodule:: grafanalib.formatunits + :members: + :undoc-members: + :show-inheritance: + +grafanalib.influxdb module +-------------------------- + +.. automodule:: grafanalib.influxdb + :members: + :undoc-members: + :show-inheritance: + grafanalib.opentsdb module -------------------------- @@ -60,7 +84,6 @@ grafanalib.zabbix module :undoc-members: :show-inheritance: - Module contents --------------- From 871df0b5c3aee81b6a02ead5f3b13e8b260569ca Mon Sep 17 00:00:00 2001 From: Puneeth Nanjundaswamy Date: Tue, 5 Jan 2021 16:37:12 +0000 Subject: [PATCH 186/403] switch to lychee --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index a4610ca0..4f823ab8 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -29,6 +29,6 @@ jobs: id: lc uses: lycheeverse/lychee-action@v1 with: - args: --exclude 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' -r docs/build/html/* + args: --exclude 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' docs/build/html/* - name: Fail if there were link errors run: exit ${{ steps.lc.outputs.exit_code }} From 9b9bfb9019312ece587364268376b909b5c22b74 Mon Sep 17 00:00:00 2001 From: Puneeth Nanjundaswamy Date: Tue, 5 Jan 2021 17:52:11 +0100 Subject: [PATCH 187/403] Revert github actions --- .github/workflows/check-sphinx-and-links.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 4f823ab8..fcb85c9a 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,8 +27,8 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1 + uses: peter-evans/link-checker@v1 with: - args: --exclude 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' docs/build/html/* + args: -x 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' -r docs/build/html/* - name: Fail if there were link errors run: exit ${{ steps.lc.outputs.exit_code }} From 3625ee7775ed67c11a06d1ab0c339e216324f072 Mon Sep 17 00:00:00 2001 From: Puneeth Nanjundaswamy Date: Tue, 5 Jan 2021 16:56:57 +0000 Subject: [PATCH 188/403] links --- grafanalib/cloudwatch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py index 6ec6d16c..15f059d9 100644 --- a/grafanalib/cloudwatch.py +++ b/grafanalib/cloudwatch.py @@ -11,7 +11,7 @@ class CloudwatchMetricsTarget(object): Generates Cloudwatch target JSON structure. Grafana docs on using Cloudwatch: - https://grafana.com/docs/features/datasources/cloudwatch/ + https://grafana.com/docs/grafana/latest/datasources/cloudwatch/ AWS docs on Cloudwatch metrics: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html From d73b73efd9879a8f6fe323a82f83d21f190893cc Mon Sep 17 00:00:00 2001 From: Puneeth Nanjundaswamy Date: Thu, 7 Jan 2021 16:46:05 +0100 Subject: [PATCH 189/403] Add changelog and remove python version check --- CHANGELOG.rst | 2 +- grafanalib/tests/test_cloudwatch.py | 7 +------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 808dfe3e..35d1c66c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,7 @@ Changelog =========== * Added Logs panel (https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/) -* ... +* Added Cloudwatch Metrics (https://grafana.com/docs/grafana/latest/datasources/cloudwatch/) Changes ------- diff --git a/grafanalib/tests/test_cloudwatch.py b/grafanalib/tests/test_cloudwatch.py index f31b57e4..b9e91bbf 100644 --- a/grafanalib/tests/test_cloudwatch.py +++ b/grafanalib/tests/test_cloudwatch.py @@ -3,12 +3,7 @@ import grafanalib.core as G import grafanalib.cloudwatch as C from grafanalib import _gen - -import sys -if sys.version_info[0] < 3: - from io import BytesIO as StringIO -else: - from io import StringIO +from io import StringIO def test_serialization_cloudwatch_metrics_target(): From b3589335f4c08a612f109661887c25d6ff717436 Mon Sep 17 00:00:00 2001 From: Puneeth Nanjundaswamy Date: Thu, 7 Jan 2021 16:46:28 +0100 Subject: [PATCH 190/403] Add changelog and remove python version check --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 35d1c66c..20f0cccf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,7 @@ Changelog =========== * Added Logs panel (https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/) -* Added Cloudwatch Metrics (https://grafana.com/docs/grafana/latest/datasources/cloudwatch/) +* Added Cloudwatch metrics datasource (https://grafana.com/docs/grafana/latest/datasources/cloudwatch/) Changes ------- From 51e624a240fb319ffa152107e0c3b54b48d91df8 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Sat, 9 Jan 2021 09:51:19 +0000 Subject: [PATCH 191/403] Add option to hide dashboard time picker closes #260 --- CHANGELOG.rst | 1 + grafanalib/core.py | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 20f0cccf..9d7e29f8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ Changelog * Added Logs panel (https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/) * Added Cloudwatch metrics datasource (https://grafana.com/docs/grafana/latest/datasources/cloudwatch/) +* Added option to hide dashboard time picker Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index 4957936f..ecbdddc7 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -818,13 +818,24 @@ def to_json_data(self): @attr.s class TimePicker(object): + """ + Time Picker + :param refreshIntervals: dashboard auto-refresh interval options + :param timeOptions: dashboard time range options + :param hidden: hide the time picker from dashboard + """ refreshIntervals = attr.ib() timeOptions = attr.ib() + hidden = attr.ib( + default=False, + validator=instance_of(bool), + ) def to_json_data(self): return { 'refresh_intervals': self.refreshIntervals, 'time_options': self.timeOptions, + 'hidden': self.hidden } From f7ebad6c4c661f48bb8a56b5de935ec021f8eb5a Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Sat, 9 Jan 2021 10:42:43 +0000 Subject: [PATCH 192/403] Remove deprecaterd attr.assoc Closes #291 --- CHANGELOG.rst | 2 +- grafanalib/core.py | 4 ++-- grafanalib/weave.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 20f0cccf..35753652 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,7 +11,7 @@ Changelog Changes ------- -* ... +* Replace deprecated attr.assoc with attr.evolve diff --git a/grafanalib/core.py b/grafanalib/core.py index 4957936f..dbbf9ad1 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1228,7 +1228,7 @@ def _iter_targets(self): yield target def _map_targets(self, f): - return attr.assoc(self, targets=[f(t) for t in self.targets]) + return attr.evolve(self, targets=[f(t) for t in self.targets]) def auto_ref_ids(self): """Give unique IDs all the panels without IDs. @@ -1250,7 +1250,7 @@ def auto_ref_ids(self): auto_ref_ids = (i for i in candidate_ref_ids if i not in ref_ids) def set_refid(t): - return t if t.refId else attr.assoc(t, refId=next(auto_ref_ids)) + return t if t.refId else attr.evolve(t, refId=next(auto_ref_ids)) return self._map_targets(set_refid) diff --git a/grafanalib/weave.py b/grafanalib/weave.py index d8313589..8ef4c9dc 100644 --- a/grafanalib/weave.py +++ b/grafanalib/weave.py @@ -56,7 +56,7 @@ def QPSGraph(data_source, title, expressions, **kwargs): def stacked(graph): """Turn a graph into a stacked graph.""" - return attr.assoc( + return attr.evolve( graph, lineWidth=0, nullPointMode=G.NULL_AS_ZERO, From aa2246889709ed3ff5dfd7c9fa2c1267ca1314fc Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Sat, 9 Jan 2021 11:50:20 +0000 Subject: [PATCH 193/403] Use pytest --strict-marker over --strict --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 8f5b6277..0b0635de 100644 --- a/tox.ini +++ b/tox.ini @@ -16,7 +16,7 @@ deps = coverage pytest commands = - python -m coverage run --rcfile=.coveragerc -m pytest --strict --maxfail=1 --ff {posargs} + python -m coverage run --rcfile=.coveragerc -m pytest --strict-markers --maxfail=1 --ff {posargs} # Had 88% test coverage at time of introducing coverage ratchet. # This number must only go up. python -m coverage report --rcfile=.coveragerc --show-missing --fail-under=88 From 633cfa8736adbadd8421093a520f56c63e8e8927 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jan 2021 07:32:53 +0000 Subject: [PATCH 194/403] Bump sphinx-rtd-theme from 0.5.0 to 0.5.1 (#306) Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 0.5.0 to 0.5.1. - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/0.5.0...0.5.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index edf3843f..aeab8787 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx == 3.4.1 -sphinx_rtd_theme == 0.5.0 +sphinx_rtd_theme == 0.5.1 From f18f3c62938d2e43857f0b7a41a20bc2f3ecbf87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Jan 2021 08:13:44 +0000 Subject: [PATCH 195/403] Bump sphinx from 3.4.1 to 3.4.3 (#305) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.4.1 to 3.4.3. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.4.1...v3.4.3) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index aeab8787..23ef03a9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.4.1 +sphinx == 3.4.3 sphinx_rtd_theme == 0.5.1 From f071d2c5415894e3ebfe44d94bfe3a30b5f6eb5e Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 15 Jan 2021 16:51:02 +0100 Subject: [PATCH 196/403] meeting day changed some time --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 51ef0b9b..c5afa22d 100644 --- a/README.rst +++ b/README.rst @@ -82,7 +82,7 @@ Community We'd like you to join the ``grafanalib`` community! Talk to us on Slack (see the links), or join us for one of our next meetings): -- Meetings take place monthly: fourth Friday of the month 15:00 UTC +- Meetings take place monthly: third Friday of the month 15:00 UTC - https://weaveworks.zoom.us/j/96824669060 - `Meeting minutes and agenda `_ From df2a8a81634fa029bedc7be18457f89d29b8a577 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 8 Feb 2021 11:40:05 +0100 Subject: [PATCH 197/403] Point to CoC from main README --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index c5afa22d..8bee7e5a 100644 --- a/README.rst +++ b/README.rst @@ -88,6 +88,7 @@ or join us for one of our next meetings): `_ (includes links to meeting recordings) +We follow the `CNCF Code of Conduct `_. Getting Help ------------ From 9626174d8a7c0889dbe597d84d441ccd90e52a16 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Feb 2021 06:07:41 +0000 Subject: [PATCH 198/403] Bump sphinx from 3.4.3 to 3.5.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.4.3 to 3.5.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.4.3...v3.5.0) Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 23ef03a9..aacdb38b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.4.3 +sphinx == 3.5.0 sphinx_rtd_theme == 0.5.1 From 7fe3222efe34f63d409e21063e05aa7c4efeec98 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Feb 2021 06:08:09 +0000 Subject: [PATCH 199/403] Bump sphinx from 3.5.0 to 3.5.1 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.5.0 to 3.5.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.5.0...v3.5.1) Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index aacdb38b..61db47b9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.5.0 +sphinx == 3.5.1 sphinx_rtd_theme == 0.5.1 From ed82afc2313d89ea1f222e475d0439f741d86f01 Mon Sep 17 00:00:00 2001 From: Olha Honcharuk Date: Fri, 5 Mar 2021 11:40:52 +0200 Subject: [PATCH 200/403] add Notification class (#317) --- CHANGELOG.rst | 1 + grafanalib/core.py | 11 +++++++++++ grafanalib/tests/examples/example.dashboard.py | 5 ++++- grafanalib/tests/test_core.py | 7 +++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b32c4bb6..a7336a5d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ Changelog * Added Logs panel (https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/) * Added Cloudwatch metrics datasource (https://grafana.com/docs/grafana/latest/datasources/cloudwatch/) * Added option to hide dashboard time picker +* Added Notification for Alert Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index 6aa794fc..8ab15a7b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -990,6 +990,17 @@ def to_json_data(self): } +@attr.s +class Notification(object): + + uid = attr.ib() + + def to_json_data(self): + return { + 'uid': self.uid, + } + + @attr.s class Dashboard(object): diff --git a/grafanalib/tests/examples/example.dashboard.py b/grafanalib/tests/examples/example.dashboard.py index bbad2c82..b2cb1ccc 100644 --- a/grafanalib/tests/examples/example.dashboard.py +++ b/grafanalib/tests/examples/example.dashboard.py @@ -1,6 +1,6 @@ from grafanalib.core import ( Alert, AlertCondition, Dashboard, Graph, - GreaterThan, OP_AND, OPS_FORMAT, Row, RTYPE_SUM, SECONDS_FORMAT, + GreaterThan, Notification, OP_AND, OPS_FORMAT, Row, RTYPE_SUM, SECONDS_FORMAT, SHORT_FORMAT, single_y_axis, Target, TimeRange, YAxes, YAxis ) @@ -59,6 +59,9 @@ reducerType=RTYPE_SUM, ), ], + notifications=[ + Notification("notification_channel_uid"), + ], ) ), Graph( diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index cc8225b1..064c536e 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -146,3 +146,10 @@ def test_logs_panel(): assert data['options']['showTime'] is False assert data['options']['wrapLogMessage'] is False assert data['options']['sortOrder'] == 'Descending' + + +def test_notification(): + uid = 'notification_channel' + notification = G.Notification(uid) + data = notification.to_json_data() + assert data['uid'] == uid From 1ae345920d571951de29a1fef2893cd84c05c0e4 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Fri, 5 Mar 2021 10:01:34 +0000 Subject: [PATCH 201/403] Add alertRuleTags field to graph panel --- CHANGELOG.rst | 1 + grafanalib/core.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a7336a5d..580fa236 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ Changelog * Added Cloudwatch metrics datasource (https://grafana.com/docs/grafana/latest/datasources/cloudwatch/) * Added option to hide dashboard time picker * Added Notification for Alert +* Added alertRuleTags field to the graph panel Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index 8ab15a7b..8cf5c23b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1211,6 +1211,7 @@ class Graph(Panel): validator=instance_of(YAxes), ) alert = attr.ib(default=None) + alertRuleTags = attr.ib(default=attr.Factory(dict)) def to_json_data(self): graphObject = { @@ -1243,6 +1244,7 @@ def to_json_data(self): } if self.alert: graphObject['alert'] = self.alert + graphObject['alertRuleTags'] = self.alertRuleTags return self.panel_json(graphObject) def _iter_targets(self): From f39ac82a9b34f42cf0c65802a559e36e4aaa4d1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Mar 2021 06:10:05 +0000 Subject: [PATCH 202/403] Bump sphinx from 3.5.1 to 3.5.2 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.5.1 to 3.5.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.5.1...v3.5.2) Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 61db47b9..690f7ab0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.5.1 +sphinx == 3.5.2 sphinx_rtd_theme == 0.5.1 From 861a3d67cf7a835cd39136f9ab39ec44688a9dd7 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 9 Mar 2021 11:32:58 +0000 Subject: [PATCH 203/403] Add note to docs about supported grafana versions --- docs/getting-started.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 90fd0bbf..7d61eee9 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -8,6 +8,14 @@ so, grafanalib is for you. grafanalib lets you generate Grafana dashboards from simple Python scripts. +Grafana migrates dashboards to the latest Grafana schema version on import, +meaning that dashboards created with grafanalib are supported by +all versions of Grafana. You may find that some of the latest features are +missing from grafanalib, please refer to the `module documentation +`_ for information +about supported features. If you find a missing feature please raise an issue +or submit a PR to the GitHub `repository `_ + Writing dashboards ================== From a1b52aaecbb1544641f89b6d19bf4c759df68cc0 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 9 Mar 2021 11:50:32 +0000 Subject: [PATCH 204/403] Move to lychee link checking action --- .github/workflows/check-sphinx-and-links.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index fcb85c9a..4f823ab8 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,8 +27,8 @@ jobs: - name: Link Checker id: lc - uses: peter-evans/link-checker@v1 + uses: lycheeverse/lychee-action@v1 with: - args: -x 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' -r docs/build/html/* + args: --exclude 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' docs/build/html/* - name: Fail if there were link errors run: exit ${{ steps.lc.outputs.exit_code }} From 6e02b07d5bed41cedcedea20ba1a5a88177a08e5 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 9 Mar 2021 12:13:04 +0000 Subject: [PATCH 205/403] Bump link checker version --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 4f823ab8..0d1a3c02 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1 + uses: lycheeverse/lychee-action@v1.0.4 with: args: --exclude 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' docs/build/html/* - name: Fail if there were link errors From 3a22c80950f9b16fd04220088c06be3cf99d19f0 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 9 Mar 2021 12:30:04 +0000 Subject: [PATCH 206/403] Test links on all html docs --- .github/workflows/check-sphinx-and-links.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 0d1a3c02..8ec44d74 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,8 +27,8 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.0.4 + uses: lycheeverse/lychee-action@v1 with: - args: --exclude 'github.com/weaveworks/grafanalib/(issue|pull)|sphinx-doc.org' docs/build/html/* + args: --verbose **/*.html - name: Fail if there were link errors run: exit ${{ steps.lc.outputs.exit_code }} From 2d7174f36dd44cb83ec75fa9141e1f31df495708 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Sat, 13 Mar 2021 09:52:10 +0000 Subject: [PATCH 207/403] Add support for gridpos (#319) Retains backwards compatibility for Rows --- .flake8 | 1 + CHANGELOG.rst | 1 + grafanalib/core.py | 196 +++++++++++++----- .../tests/examples/example.dashboard.py | 50 +++-- grafanalib/tests/test_grafanalib.py | 69 ++++++ 5 files changed, 237 insertions(+), 80 deletions(-) diff --git a/.flake8 b/.flake8 index e44b8108..813ffb91 100644 --- a/.flake8 +++ b/.flake8 @@ -1,2 +1,3 @@ [flake8] +max-line-length = 120 ignore = E501 diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 580fa236..d4af63b1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ Changelog * Added option to hide dashboard time picker * Added Notification for Alert * Added alertRuleTags field to the graph panel +* Added support for using gridPos for dashboard panels Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index 8cf5c23b..dba6528b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -74,6 +74,7 @@ def to_json_data(self): ABSOLUTE_TYPE = 'absolute' DASHBOARD_TYPE = 'dashboard' +ROW_TYPE = 'row' GRAPH_TYPE = 'graph' STAT_TYPE = 'stat' SINGLESTAT_TYPE = 'singlestat' @@ -539,46 +540,28 @@ def _balance_panels(panels): @attr.s -class Row(object): - # TODO: jml would like to separate the balancing behaviour from this - # layer. - panels = attr.ib(default=attr.Factory(list), converter=_balance_panels) - collapse = attr.ib( - default=False, validator=instance_of(bool), - ) - editable = attr.ib( - default=True, validator=instance_of(bool), - ) - height = attr.ib( - default=attr.Factory(lambda: DEFAULT_ROW_HEIGHT), - validator=instance_of(Pixels), - ) - showTitle = attr.ib(default=None) - title = attr.ib(default=None) - repeat = attr.ib(default=None) - - def _iter_panels(self): - return iter(self.panels) +class GridPos(object): + """GridPos describes the panel size and position in grid coordinates. + + :param h: height of the panel, grid height units each represents + 30 pixels + :param w: width of the panel 1-24 (the width of the dashboard + is divided into 24 columns) + :param x: x cordinate of the panel, in same unit as w + :param y: y cordinate of the panel, in same unit as h + """ - def _map_panels(self, f): - return attr.evolve(self, panels=list(map(f, self.panels))) + h = attr.ib() + w = attr.ib() + x = attr.ib() + y = attr.ib() def to_json_data(self): - showTitle = False - title = "New row" - if self.title is not None: - showTitle = True - title = self.title - if self.showTitle is not None: - showTitle = self.showTitle return { - 'collapse': self.collapse, - 'editable': self.editable, - 'height': self.height, - 'panels': self.panels, - 'showTitle': showTitle, - 'title': title, - 'repeat': self.repeat, + 'h': self.h, + 'w': self.w, + 'x': self.x, + 'y': self.y } @@ -1005,7 +988,6 @@ def to_json_data(self): class Dashboard(object): title = attr.ib() - rows = attr.ib() annotations = attr.ib( default=attr.Factory(Annotations), validator=instance_of(Annotations), @@ -1023,7 +1005,9 @@ class Dashboard(object): id = attr.ib(default=None) inputs = attr.ib(default=attr.Factory(list)) links = attr.ib(default=attr.Factory(list)) + panels = attr.ib(default=attr.Factory(list), validator=instance_of(list)) refresh = attr.ib(default=DEFAULT_REFRESH) + rows = attr.ib(default=attr.Factory(list), validator=instance_of(list)) schemaVersion = attr.ib(default=SCHEMA_VERSION) sharedCrosshair = attr.ib( default=False, @@ -1052,8 +1036,20 @@ def _iter_panels(self): for panel in row._iter_panels(): yield panel + for panel in self.panels: + if hasattr(panel, 'panels'): + yield panel + for row_panel in panel._iter_panels(): + yield panel + else: + yield panel + def _map_panels(self, f): - return attr.evolve(self, rows=[r._map_panels(f) for r in self.rows]) + return attr.evolve( + self, + rows=[r._map_panels(f) for r in self.rows], + panels=[p._map_panels(f) for p in self.panels] + ) def auto_panel_ids(self): """Give unique IDs all the panels without IDs. @@ -1071,6 +1067,11 @@ def set_id(panel): return self._map_panels(set_id) def to_json_data(self): + if self.panels and self.rows: + print( + "You are using both panels and rows in this dashboard, please use one or the other. " + "Panels should be used in preference over rows, see example dashboard for help." + ) return { '__inputs': self.inputs, 'annotations': self.annotations, @@ -1080,6 +1081,7 @@ def to_json_data(self): 'hideControls': self.hideControls, 'id': self.id, 'links': self.links, + 'panels': self.panels if not self.rows else [], 'refresh': self.refresh, 'rows': self.rows, 'schemaVersion': self.schemaVersion, @@ -1128,6 +1130,7 @@ class Panel(object): editable = attr.ib(default=True, validator=instance_of(bool)) error = attr.ib(default=False, validator=instance_of(bool)) height = attr.ib(default=None) + gridPos = attr.ib(default=None) hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) id = attr.ib(default=None) interval = attr.ib(default=None) @@ -1140,32 +1143,111 @@ class Panel(object): timeShift = attr.ib(default=None) transparent = attr.ib(default=False, validator=instance_of(bool)) + def _map_panels(self, f): + return f(self) + def panel_json(self, overrides): res = { - "cacheTimeout": self.cacheTimeout, - "datasource": self.dataSource, - "description": self.description, - "editable": self.editable, - "error": self.error, - "height": self.height, - "hideTimeOverride": self.hideTimeOverride, - "id": self.id, - "interval": self.interval, - "links": self.links, - "maxDataPoints": self.maxDataPoints, - "minSpan": self.minSpan, - "repeat": self.repeat, - "span": self.span, - "targets": self.targets, - "timeFrom": self.timeFrom, - "timeShift": self.timeShift, - "title": self.title, - "transparent": self.transparent, + 'cacheTimeout': self.cacheTimeout, + 'datasource': self.dataSource, + 'description': self.description, + 'editable': self.editable, + 'error': self.error, + 'height': self.height, + 'gridPos': self.gridPos, + 'hideTimeOverride': self.hideTimeOverride, + 'id': self.id, + 'interval': self.interval, + 'links': self.links, + 'maxDataPoints': self.maxDataPoints, + 'minSpan': self.minSpan, + 'repeat': self.repeat, + 'span': self.span, + 'targets': self.targets, + 'timeFrom': self.timeFrom, + 'timeShift': self.timeShift, + 'title': self.title, + 'transparent': self.transparent, } res.update(overrides) return res +@attr.s +class RowPanel(Panel): + """ + Generates Row panel json structure. + + :param title: title of the panel + :param panels: list of panels in the row + """ + + panels = attr.ib(default=attr.Factory(list), validator=instance_of(list)) + + def _iter_panels(self): + return iter(self.panels) + + def _map_panels(self, f): + self = f(self) + return attr.evolve(self, panels=list(map(f, self.panels))) + + def to_json_data(self): + return self.panel_json( + { + 'collapsed': False, + 'panels': self.panels, + 'type': ROW_TYPE + } + ) + + +@attr.s +class Row(object): + """ + Legacy support for old row, when not used with gridpos + """ + # TODO: jml would like to separate the balancing behaviour from this + # layer. + panels = attr.ib(default=attr.Factory(list), converter=_balance_panels) + collapse = attr.ib( + default=False, validator=instance_of(bool), + ) + editable = attr.ib( + default=True, validator=instance_of(bool), + ) + height = attr.ib( + default=attr.Factory(lambda: DEFAULT_ROW_HEIGHT), + validator=instance_of(Pixels), + ) + showTitle = attr.ib(default=None) + title = attr.ib(default=None) + repeat = attr.ib(default=None) + + def _iter_panels(self): + return iter(self.panels) + + def _map_panels(self, f): + return attr.evolve(self, panels=list(map(f, self.panels))) + + def to_json_data(self): + showTitle = False + title = "New row" + if self.title is not None: + showTitle = True + title = self.title + if self.showTitle is not None: + showTitle = self.showTitle + return { + 'collapse': self.collapse, + 'editable': self.editable, + 'height': self.height, + 'panels': self.panels, + 'showTitle': showTitle, + 'title': title, + 'repeat': self.repeat, + } + + @attr.s class Graph(Panel): """ diff --git a/grafanalib/tests/examples/example.dashboard.py b/grafanalib/tests/examples/example.dashboard.py index b2cb1ccc..7172da95 100644 --- a/grafanalib/tests/examples/example.dashboard.py +++ b/grafanalib/tests/examples/example.dashboard.py @@ -1,16 +1,19 @@ from grafanalib.core import ( - Alert, AlertCondition, Dashboard, Graph, - GreaterThan, Notification, OP_AND, OPS_FORMAT, Row, RTYPE_SUM, SECONDS_FORMAT, + Alert, AlertCondition, Dashboard, Graph, GridPos, + GreaterThan, Notification, OP_AND, OPS_FORMAT, RowPanel, RTYPE_SUM, SECONDS_FORMAT, SHORT_FORMAT, single_y_axis, Target, TimeRange, YAxes, YAxis ) dashboard = Dashboard( title="Frontend Stats", - rows=[ - Row(panels=[ - Graph( - title="Frontend QPS", + panels=[ + RowPanel( + title="New row", + gridPos=GridPos(h=1, w=24, x=0, y=8) + ), + Graph( + title="Frontend QPS", dataSource='My Prometheus', targets=[ Target( @@ -61,26 +64,27 @@ ], notifications=[ Notification("notification_channel_uid"), - ], - ) + ] ), - Graph( - title="Frontend latency", - dataSource='My Prometheus', - targets=[ - Target( + gridPos=GridPos(h=1, w=12, x=0, y=9) + ), + Graph( + title="Frontend latency", + dataSource='My Prometheus', + targets=[ + Target( expr='histogram_quantile(0.5, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', legendFormat="0.5 quantile", refId='A', - ), - Target( - expr='histogram_quantile(0.99, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', - legendFormat="0.99 quantile", - refId='B', - ), - ], - yAxes=single_y_axis(format=SECONDS_FORMAT), - ), - ]), + ), + Target( + expr='histogram_quantile(0.99, sum(irate(nginx_http_request_duration_seconds_bucket{job="default/frontend"}[1m])) by (le))', + legendFormat="0.99 quantile", + refId='B', + ), + ], + yAxes=single_y_axis(format=SECONDS_FORMAT), + gridPos=GridPos(h=8, w=12, x=0, y=0) + ) ], ).auto_panel_ids() diff --git a/grafanalib/tests/test_grafanalib.py b/grafanalib/tests/test_grafanalib.py index a5a67046..eeb28bba 100644 --- a/grafanalib/tests/test_grafanalib.py +++ b/grafanalib/tests/test_grafanalib.py @@ -61,6 +61,30 @@ def test_auto_id(): ).auto_panel_ids() assert dashboard.rows[0].panels[0].id == 1 + dashboard = G.Dashboard( + title="Test dashboard", + panels=[ + G.RowPanel(gridPos=G.GridPos(h=1, w=24, x=0, y=8)), + G.Graph( + title="CPU Usage by Namespace (rate[5m])", + dataSource="My data source", + targets=[ + G.Target( + expr='whatever', + legendFormat='{{namespace}}', + refId='A', + ), + ], + yAxes=G.YAxes( + G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds"), + G.YAxis(format=G.SHORT_FORMAT), + ), + gridPos=G.GridPos(h=1, w=24, x=0, y=8) + ) + ], + ).auto_panel_ids() + assert dashboard.panels[0].id == 1 + def test_auto_refids_preserves_provided_ids(): """ @@ -95,6 +119,38 @@ def test_auto_refids_preserves_provided_ids(): assert dashboard.rows[0].panels[0].targets[1].refId == 'Q' assert dashboard.rows[0].panels[0].targets[2].refId == 'B' + dashboard = G.Dashboard( + title="Test dashboard", + panels=[ + G.Graph( + title="CPU Usage by Namespace (rate[5m])", + dataSource="My data source", + targets=[ + G.Target( + expr='whatever #Q', + legendFormat='{{namespace}}', + ), + G.Target( + expr='hidden whatever', + legendFormat='{{namespace}}', + refId='Q', + ), + G.Target( + expr='another target' + ), + ], + yAxes=G.YAxes( + G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds"), + G.YAxis(format=G.SHORT_FORMAT), + ), + gridPos=G.GridPos(h=1, w=24, x=0, y=8) + ).auto_ref_ids() + ], + ).auto_panel_ids() + assert dashboard.panels[0].targets[0].refId == 'A' + assert dashboard.panels[0].targets[1].refId == 'Q' + assert dashboard.panels[0].targets[2].refId == 'B' + def test_auto_refids(): """ @@ -132,3 +188,16 @@ def test_row_show_title(): row = G.Row(title='My title', showTitle=False).to_json_data() assert row['title'] == 'My title' assert not row['showTitle'] + + +def test_row_panel_show_title(): + row = G.RowPanel().to_json_data() + assert row['title'] == '' + assert row['panels'] == [] + + row = G.RowPanel(title='My title').to_json_data() + assert row['title'] == 'My title' + + row = G.RowPanel(title='My title', panels=['a', 'b']).to_json_data() + assert row['title'] == 'My title' + assert row['panels'][0] == 'a' From adf7726e513766c3ec217172f7b33b92045478e3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Mar 2021 07:25:41 +0000 Subject: [PATCH 208/403] Bump lycheeverse/lychee-action from v1 to v1.0.4 (#329) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from v1 to v1.0.4. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1...e02bac8ef29b549872876f52063681eafebd6be6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 8ec44d74..1824e9a3 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1 + uses: lycheeverse/lychee-action@v1.0.4 with: args: --verbose **/*.html - name: Fail if there were link errors From 662b338cac3020d69300843cf4cad6a4b48db03a Mon Sep 17 00:00:00 2001 From: Jeremiah Williams Date: Mon, 15 Mar 2021 09:02:46 -0700 Subject: [PATCH 209/403] Adding humio datasource. (#328) * Adding humio datasource. This is a plugin datasource, documented here https://grafana.com/grafana/plugins/humio-datasource/. * cleaning up files and fixing typo Co-authored-by: Jeremiah Williams --- CHANGELOG.rst | 9 +++++++++ grafanalib/humio.py | 30 ++++++++++++++++++++++++++++++ grafanalib/tests/test_humio.py | 25 +++++++++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 grafanalib/humio.py create mode 100644 grafanalib/tests/test_humio.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d4af63b1..0db95633 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,15 @@ Changelog ========= +0.5.10 (2021-03-12) +================== + +Changes +------- + +* Added support for Humio Data Source. (https://grafana.com/grafana/plugins/humio-datasource/) + + 0.x.x (TBD) =========== diff --git a/grafanalib/humio.py b/grafanalib/humio.py new file mode 100644 index 00000000..4e3609ab --- /dev/null +++ b/grafanalib/humio.py @@ -0,0 +1,30 @@ +"""Helpers to create Humio-specific Grafana queries.""" + +import attr + + +@attr.s +class HumioTarget(object): + """ + Generates Humio target JSON structure. + + Link to Humio Grafana plugin https://grafana.com/grafana/plugins/humio-datasource/ + + Humio docs on query language https://docs.humio.com/reference/language-syntax/ + + :param humioQuery: Query that will be executed on Humio + :param humioRepository: Repository to execute query on. + :param refId: target reference id + """ + + humioQuery = attr.ib(default="") + humioRepository = attr.ib(default="") + refId = attr.ib(default="") + + def to_json_data(self): + + return { + "humioQuery": self.humioQuery, + "humioRepository": self.humioRepository, + "refId": self.refId + } diff --git a/grafanalib/tests/test_humio.py b/grafanalib/tests/test_humio.py new file mode 100644 index 00000000..2551cfb8 --- /dev/null +++ b/grafanalib/tests/test_humio.py @@ -0,0 +1,25 @@ +"""Tests for Humio Datasource""" + +import grafanalib.core as G +import grafanalib.humio as H +from grafanalib import _gen +from io import StringIO + + +def test_serialization_humio_metrics_target(): + """Serializing a graph doesn't explode.""" + graph = G.Graph( + title="Humio Logs", + dataSource="Humio data source", + targets=[ + H.HumioTarget(), + ], + id=1, + yAxes=G.YAxes( + G.YAxis(format=G.SHORT_FORMAT, label="ms"), + G.YAxis(format=G.SHORT_FORMAT), + ), + ) + stream = StringIO() + _gen.write_dashboard(graph, stream) + assert stream.getvalue() != '' From 376e7f201ce40859556c38db0d766237e31ae5ea Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Wed, 17 Mar 2021 09:26:38 +0000 Subject: [PATCH 210/403] Add elasticsearch alert condition override (#324) * Add elasticsearch alert condition override * Fix test issue with optional args --- CHANGELOG.rst | 1 + grafanalib/core.py | 2 +- grafanalib/elasticsearch.py | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0db95633..577703cc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,7 @@ Changes * Added option to hide dashboard time picker * Added Notification for Alert * Added alertRuleTags field to the graph panel +* Added support for Elasticsearch alert condition * Added support for using gridPos for dashboard panels Changes diff --git a/grafanalib/core.py b/grafanalib/core.py index dba6528b..833bdc6e 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -923,7 +923,7 @@ class AlertCondition(object): timeRange = attr.ib(validator=instance_of(TimeRange)) operator = attr.ib() reducerType = attr.ib() - type = attr.ib(default=CTYPE_QUERY) + type = attr.ib(default=CTYPE_QUERY, kw_only=True) def to_json_data(self): queryParams = [ diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index d18cdd4d..ec3b3010 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -3,6 +3,7 @@ import attr import itertools from attr.validators import instance_of +from grafanalib.core import AlertCondition DATE_HISTOGRAM_DEFAULT_FIELD = 'time_iso8601' ORDER_ASC = 'asc' @@ -362,3 +363,26 @@ def to_json_data(self): 'query': self.query, 'refId': self.refId, } + + +@attr.s +class ElasticsearchAlertCondition(AlertCondition): + """ + Override alert condition to support Elasticseach target. + + See AlertCondition for more information. + + :param Target target: Metric the alert condition is based on. + :param Evaluator evaluator: How we decide whether we should alert on the + metric. e.g. ``GreaterThan(5)`` means the metric must be greater than 5 + to trigger the condition. See ``GreaterThan``, ``LowerThan``, + ``WithinRange``, ``OutsideRange``, ``NoValue``. + :param TimeRange timeRange: How long the condition must be true for before + we alert. + :param operator: One of ``OP_AND`` or ``OP_OR``. How this condition + combines with other conditions. + :param reducerType: RTYPE_* + :param type: CTYPE_* + """ + + target = attr.ib(validator=instance_of(ElasticsearchTarget)) From f4996e2da6ccf662c45f7320d1f3b22e7d52454c Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Wed, 17 Mar 2021 09:28:39 +0000 Subject: [PATCH 211/403] Add support for thresholds to graph panels (#326) * Add support for thresholds to graph panels * Add seperate class for GraphThresholds * Add tests * Only add line and fill color if custom colorMode in use * Do not add threshold if alert defined --- CHANGELOG.rst | 1 + grafanalib/core.py | 96 ++++++++++++++++++++++++++++++----- grafanalib/tests/test_core.py | 80 +++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 577703cc..0d5174e4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,7 @@ Changes * Added option to hide dashboard time picker * Added Notification for Alert * Added alertRuleTags field to the graph panel +* Added support for thresholds to graph panel * Added support for Elasticsearch alert condition * Added support for using gridPos for dashboard panels diff --git a/grafanalib/core.py b/grafanalib/core.py index 833bdc6e..097a14af 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1069,7 +1069,7 @@ def set_id(panel): def to_json_data(self): if self.panels and self.rows: print( - "You are using both panels and rows in this dashboard, please use one or the other. " + "Warning: You are using both panels and rows in this dashboard, please use one or the other. " "Panels should be used in preference over rows, see example dashboard for help." ) return { @@ -1253,12 +1253,17 @@ class Graph(Panel): """ Generates Graph panel json structure. - :param dataLinks: list of data links hooked to datapoints on the graph + :param alert: List of AlertConditions + :param alertRuleTags: Key Value pairs to be sent with Alert notifications + :param dataLinks: List of data links hooked to datapoints on the graph :param dataSource: DataSource's name :param minSpan: Minimum width for each panel :param repeat: Template's name to repeat Graph on + :param thresholds: List of GraphThresholds - Only valid when alert not defined """ + alert = attr.ib(default=None) + alertRuleTags = attr.ib(default=attr.Factory(dict)) alertThreshold = attr.ib(default=True, validator=instance_of(bool)) aliasColors = attr.ib(default=attr.Factory(dict)) bars = attr.ib(default=False, validator=instance_of(bool)) @@ -1285,6 +1290,7 @@ class Graph(Panel): default=attr.Factory(Tooltip), validator=instance_of(Tooltip), ) + thresholds = attr.ib(default=attr.Factory(list)) xAxis = attr.ib(default=attr.Factory(XAxis), validator=instance_of(XAxis)) # XXX: This isn't a *good* default, rather it's the default Grafana uses. yAxes = attr.ib( @@ -1292,8 +1298,6 @@ class Graph(Panel): converter=to_y_axes, validator=instance_of(YAxes), ) - alert = attr.ib(default=None) - alertRuleTags = attr.ib(default=attr.Factory(dict)) def to_json_data(self): graphObject = { @@ -1320,6 +1324,7 @@ def to_json_data(self): 'stack': self.stack, 'steppedLine': self.steppedLine, 'tooltip': self.tooltip, + 'thresholds': self.thresholds, 'type': GRAPH_TYPE, 'xaxis': self.xAxis, 'yaxes': self.yAxes, @@ -1327,6 +1332,9 @@ def to_json_data(self): if self.alert: graphObject['alert'] = self.alert graphObject['alertRuleTags'] = self.alertRuleTags + graphObject['thresholds'] = [] + if self.thresholds and self.alert: + print("Warning: Graph threshold ignored as Alerts defined") return self.panel_json(graphObject) def _iter_targets(self): @@ -2392,7 +2400,8 @@ def to_json_data(self): @attr.s class Statusmap(Panel): - """Generates json structure for the flant-statusmap-panel visualisation plugin (https://grafana.com/grafana/plugins/flant-statusmap-panel/). + """Generates json structure for the flant-statusmap-panel visualisation plugin + (https://grafana.com/grafana/plugins/flant-statusmap-panel/). :param alert :param cards: A statusmap card object: keys 'cardRound', 'cardMinWidth', 'cardHSpacing', 'cardVSpacing' @@ -2484,7 +2493,8 @@ class Svg(Panel): :param id: panel id :param interval: defines time interval between metric queries :param links: additional web links - :param reduceCalc: algorithm for reduction to a single value: keys 'mean' 'lastNotNull' 'last' 'first' 'firstNotNull' 'min' 'max' 'sum' 'total' + :param reduceCalc: algorithm for reduction to a single value: + keys 'mean' 'lastNotNull' 'last' 'first' 'firstNotNull' 'min' 'max' 'sum' 'total' :param span: defines the number of spans that will be used for panel :param svgFilePath: path to SVG image file to be displayed """ @@ -2586,7 +2596,8 @@ class DashboardList(Panel): :param transparent: defines if the panel is transparent :param showHeadings: The chosen list selection (Starred, Recently viewed, Search) is shown as a heading - :param showSearch: Display dashboards by search query or tags. Requires you to enter at least one value in Query or Tags + :param showSearch: Display dashboards by search query or tags. + Requires you to enter at least one value in Query or Tags :param showRecent: Display recently viewed dashboards in alphabetical order :param showStarred: Display starred dashboards in alphabetical order :param maxItems: Sets the maximum number of items to list per section @@ -2638,7 +2649,8 @@ class Logs(Panel): :param showLabels: Show or hide the unique labels column, which shows only non-common labels :param showTime: Show or hide the log timestamp column :param wrapLogMessages: Toggle line wrapping - :param sortOrder: Display results in 'Descending' or 'Ascending' time order. The default is Descending, showing the newest logs first. + :param sortOrder: Display results in 'Descending' or 'Ascending' time order. The default is Descending, + showing the newest logs first. """ showLabels = attr.ib(default=False, validator=instance_of(bool)) showTime = attr.ib(default=False, validator=instance_of(bool)) @@ -2667,11 +2679,21 @@ def to_json_data(self): @attr.s class Threshold(object): - """Threshold for a gauge - - :param color: color of threshold - :param index: index of color in gauge - :param value: when to use this color will be null if index is 0 + """Threshold for for panels + (https://grafana.com/docs/grafana/latest/panels/thresholds/) + + :param color: Color of threshold + :param index: Index of color in panel + :param line: Display Threshold line, defaults to True + :param value: When to use this color will be null if index is 0 + :param op: EVAL_LT for less than or EVAL_GT for greater than to indicate what the threshold applies to. + :param yaxis: Choose left or right for panels + + Example: + thresholds = [ + Threshold('green', 0, 0.0), + Threshold('red', 1, 80.0) + ] """ color = attr.ib() @@ -2692,6 +2714,54 @@ def to_json_data(self): } +@attr.s +class GraphThreshold(object): + """Threshold for for Graph panel + (https://grafana.com/docs/grafana/latest/panels/thresholds/) + + :param colorMode: Color mode of the threshold, value can be `ok`, `warning`, `critical` or `custom`. + If `custom` is selcted a lineColor and fillColor should be provided + :param fill: Display threshold fill, defaults to True + :param line: Display threshold line, defaults to True + :param value: When to use this color will be null if index is 0 + :param op: EVAL_LT for less than or EVAL_GT for greater than to indicate what the threshold applies to. + :param yaxis: Choose left or right for Graph panels + :param fillColor: Fill color of the threshold, when colorMode = "custom" + :param lineColor: Line color of the threshold, when colorMode = "custom" + + Example: + thresholds = [ + GraphThreshold(colorMode="ok", value=10.0), + GrpahThreshold(colorMode="critical", value=90.0) + ] + """ + + value = attr.ib(validator=instance_of(float)) + colorMode = attr.ib(default="critical") + fill = attr.ib(default=True, validator=instance_of(bool)) + line = attr.ib(default=True, validator=instance_of(bool)) + op = attr.ib(default=EVAL_GT) + yaxis = attr.ib(default='left') + fillColor = attr.ib(default=RED) + lineColor = attr.ib(default=RED) + + def to_json_data(self): + data = { + 'value': self.value, + 'colorMode': self.colorMode, + 'fill': self.fill, + 'line': self.line, + 'op': self.op, + 'yaxis': self.yaxis, + } + + if self.colorMode == "custom": + data['fillColor'] = self.fillColor + data['lineColor'] = self.lineColor + + return data + + @attr.s class SeriesOverride(object): alias = attr.ib() diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 064c536e..14ea54eb 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -153,3 +153,83 @@ def test_notification(): notification = G.Notification(uid) data = notification.to_json_data() assert data['uid'] == uid + + +def test_graph_panel(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + graph = G.Graph(data_source, targets, title) + data = graph.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title + assert 'alert' not in data + + +def test_graph_panel_threshold(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + thresholds = [ + G.GraphThreshold(20.0), + G.GraphThreshold(40.2, colorMode="ok") + ] + graph = G.Graph(data_source, targets, title, thresholds=thresholds) + data = graph.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title + assert 'alert' not in data + assert data['thresholds'] == thresholds + + +def test_graph_panel_alert(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + alert = [ + G.AlertCondition(G.Target(), G.Evaluator('a', 'b'), G.TimeRange('5', '6'), 'd', 'e') + ] + thresholds = [ + G.GraphThreshold(20.0), + G.GraphThreshold(40.2, colorMode="ok") + ] + graph = G.Graph(data_source, targets, title, thresholds=thresholds, alert=alert) + data = graph.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title + assert data['alert'] == alert + assert data['thresholds'] == [] + + +def test_graph_threshold(): + value = 20.0 + colorMode = "ok" + threshold = G.GraphThreshold(value, colorMode=colorMode) + data = threshold.to_json_data() + + assert data['value'] == value + assert data['colorMode'] == colorMode + assert data['fill'] is True + assert data['line'] is True + assert data['op'] == G.EVAL_GT + assert 'fillColor' not in data + assert 'lineColor' not in data + + +def test_graph_threshold_custom(): + value = 20.0 + colorMode = "custom" + color = G.GREEN + threshold = G.GraphThreshold(value, colorMode=colorMode, fillColor=color) + data = threshold.to_json_data() + + assert data['value'] == value + assert data['colorMode'] == colorMode + assert data['fill'] is True + assert data['line'] is True + assert data['op'] == G.EVAL_GT + assert data['fillColor'] == color + assert data['lineColor'] == G.RED From ba2b7f9bb69828d2b5fe5bb907159e5bd8ea8028 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Thu, 18 Mar 2021 09:19:43 +0000 Subject: [PATCH 212/403] Enable use with pre-17.4.0 versions of attr (#321) * Enable use with pre-17.4.0 versions of attr. PR #180 updated Grafanalib to work with attr versions post 19.2.0, when keyword argument convert to attr.ib() was removed. It had been deprecated in 17.4.0, and renamed to converter. This change revises the change to work with both varieties of attr.ib(), and so removes version requirements for attr. Those working on antediluvian systems can use whatever system packaged version of attr is available, a convenience when packaging Grafanalib. * Set minimum supported attrs verison Co-authored-by: Jim Hague --- grafanalib/core.py | 22 ++++++++++++++++------ grafanalib/zabbix.py | 14 ++++++++++---- setup.py | 2 +- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 097a14af..8d659cae 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1208,7 +1208,10 @@ class Row(object): """ # TODO: jml would like to separate the balancing behaviour from this # layer. - panels = attr.ib(default=attr.Factory(list), converter=_balance_panels) + try: + panels = attr.ib(default=attr.Factory(list), converter=_balance_panels) + except TypeError: + panels = attr.ib(default=attr.Factory(list), convert=_balance_panels) collapse = attr.ib( default=False, validator=instance_of(bool), ) @@ -1293,11 +1296,18 @@ class Graph(Panel): thresholds = attr.ib(default=attr.Factory(list)) xAxis = attr.ib(default=attr.Factory(XAxis), validator=instance_of(XAxis)) # XXX: This isn't a *good* default, rather it's the default Grafana uses. - yAxes = attr.ib( - default=attr.Factory(YAxes), - converter=to_y_axes, - validator=instance_of(YAxes), - ) + try: + yAxes = attr.ib( + default=attr.Factory(YAxes), + converter=to_y_axes, + validator=instance_of(YAxes), + ) + except TypeError: + yAxes = attr.ib( + default=attr.Factory(YAxes), + convert=to_y_axes, + validator=instance_of(YAxes), + ) def to_json_data(self): graphObject = { diff --git a/grafanalib/zabbix.py b/grafanalib/zabbix.py index fa8ca9d1..085532ea 100644 --- a/grafanalib/zabbix.py +++ b/grafanalib/zabbix.py @@ -819,10 +819,16 @@ class ZabbixTriggersPanel(object): span = attr.ib(default=None) statusField = attr.ib(default=False, validator=instance_of(bool)) transparent = attr.ib(default=False, validator=instance_of(bool)) - triggerSeverity = attr.ib( - default=ZABBIX_SEVERITY_COLORS, - converter=convertZabbixSeverityColors, - ) + try: + triggerSeverity = attr.ib( + default=ZABBIX_SEVERITY_COLORS, + converter=convertZabbixSeverityColors, + ) + except TypeError: + triggerSeverity = attr.ib( + default=ZABBIX_SEVERITY_COLORS, + convert=convertZabbixSeverityColors, + ) triggers = attr.ib( default=attr.Factory(ZabbixTrigger), validator=instance_of(ZabbixTrigger), diff --git a/setup.py b/setup.py index 32f784ab..3a1179fd 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ def local_file(name): 'Topic :: System :: Monitoring', ], install_requires=[ - 'attrs==20.3.0', + 'attrs>=15.2.0', ], extras_require={ 'dev': [ From 02a05f86d59b78d6a208cd54b3868b14986057cf Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Thu, 18 Mar 2021 09:20:37 +0000 Subject: [PATCH 213/403] Prep for 0.5.10 release (#330) * Prep for 0.5.10 release * Fix changelog --- CHANGELOG.rst | 14 +++++++++----- setup.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0d5174e4..6f61f66d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,17 +2,19 @@ Changelog ========= -0.5.10 (2021-03-12) -================== +0.x.x (TBD) +=========== + +* ... Changes ------- -* Added support for Humio Data Source. (https://grafana.com/grafana/plugins/humio-datasource/) +* ... -0.x.x (TBD) -=========== +0.5.10 (2021-03-21) +================== * Added Logs panel (https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/) * Added Cloudwatch metrics datasource (https://grafana.com/docs/grafana/latest/datasources/cloudwatch/) @@ -22,6 +24,8 @@ Changes * Added support for thresholds to graph panel * Added support for Elasticsearch alert condition * Added support for using gridPos for dashboard panels +* Added support for Humio Data Source. (https://grafana.com/grafana/plugins/humio-datasource/) + Changes ------- diff --git a/setup.py b/setup.py index 3a1179fd..4e773f6a 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.9', + version='0.5.10', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 11bb792cb591670d2b66fbd11e9712f272fb0d47 Mon Sep 17 00:00:00 2001 From: Charles OConor Date: Sat, 20 Mar 2021 02:16:12 -0700 Subject: [PATCH 214/403] Fix Repeat for Panel (#331) * Fix Repeat for Pannel * Remove other instances/refs to rows --- grafanalib/core.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 8d659cae..6af6ee61 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1137,7 +1137,7 @@ class Panel(object): links = attr.ib(default=attr.Factory(list)) maxDataPoints = attr.ib(default=100) minSpan = attr.ib(default=None) - repeat = attr.ib(default=None) + repeat = attr.ib(default=attr.Factory(Repeat), validator=instance_of(Repeat)) span = attr.ib(default=None) timeFrom = attr.ib(default=None) timeShift = attr.ib(default=None) @@ -1161,7 +1161,9 @@ def panel_json(self, overrides): 'links': self.links, 'maxDataPoints': self.maxDataPoints, 'minSpan': self.minSpan, - 'repeat': self.repeat, + 'repeat': self.repeat.variable, + 'repeatDirection': self.repeat.direction, + 'maxPerRow': self.repeat.maxPerRow, 'span': self.span, 'targets': self.targets, 'timeFrom': self.timeFrom, @@ -1261,7 +1263,6 @@ class Graph(Panel): :param dataLinks: List of data links hooked to datapoints on the graph :param dataSource: DataSource's name :param minSpan: Minimum width for each panel - :param repeat: Template's name to repeat Graph on :param thresholds: List of GraphThresholds - Only valid when alert not defined """ @@ -1529,7 +1530,6 @@ class Stat(Panel): :param span: defines the number of spans that will be used for panel :param thresholds: single stat thresholds :param transparent: defines if the panel should be transparent - :param repeat: defines how the panel should be repeated """ textMode = attr.ib(default='auto') @@ -1543,7 +1543,6 @@ class Stat(Panel): thresholds = attr.ib(default="") reduceCalc = attr.ib(default='mean', type=str) decimals = attr.ib(default=None) - repeat = attr.ib(default=attr.Factory(Repeat), validator=instance_of(Repeat)) def to_json_data(self): return self.panel_json( @@ -1575,9 +1574,6 @@ def to_json_data(self): } }, 'type': STAT_TYPE, - 'repeat': self.repeat.variable, - 'repeatDirection': self.repeat.direction, - 'maxPerRow': self.repeat.maxPerRow } ) From ce9a8a6353297da6e4632db09ad29491613fd5a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Mar 2021 07:37:44 +0000 Subject: [PATCH 215/403] Bump sphinx from 3.5.2 to 3.5.3 (#332) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.5.2 to 3.5.3. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/3.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 690f7ab0..64d3a009 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.5.2 +sphinx == 3.5.3 sphinx_rtd_theme == 0.5.1 From 082ee400594aa6d3eceb4a90bb2b225c14324f54 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Mar 2021 07:38:03 +0000 Subject: [PATCH 216/403] Bump lycheeverse/lychee-action from v1.0.4 to v1.0.6 (#333) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from v1.0.4 to v1.0.6. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.0.4...e8f5f9c353907e8d2d5bc8b4e2ee70f016fb6ce7) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 1824e9a3..9b0ac71c 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.0.4 + uses: lycheeverse/lychee-action@v1.0.6 with: args: --verbose **/*.html - name: Fail if there were link errors From 0d15f65ac401d4b0499d4e91c8ab383fb2fbc468 Mon Sep 17 00:00:00 2001 From: dafna-starkware <61111179+dafna-starkware@users.noreply.github.com> Date: Wed, 24 Mar 2021 16:12:31 +0200 Subject: [PATCH 217/403] =?UTF-8?q?Add=20timeField=20field=20for=20the=20E?= =?UTF-8?q?lasticsearch=20target=20to=20allow=20the=20alert=20t=E2=80=A6?= =?UTF-8?q?=20(#334)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add timeField field for the Elasticsearch target to allow the alert to change its state. * Update CHANGELOG.rst Co-authored-by: JamesGibo --- CHANGELOG.rst | 2 +- grafanalib/elasticsearch.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6f61f66d..5ac2fcad 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,7 +5,7 @@ Changelog 0.x.x (TBD) =========== -* ... +* Added timeField field for the Elasticsearch target to allow the alert to change its state Changes ------- diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index ec3b3010..242dc48b 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -320,6 +320,7 @@ class ElasticsearchTarget(object): :param metricAggs: Metric Aggregators :param query: query :param refId: target reference id + :param timeField: name of the elasticsearch time field """ alias = attr.ib(default=None) @@ -329,6 +330,7 @@ class ElasticsearchTarget(object): metricAggs = attr.ib(default=attr.Factory(lambda: [CountMetricAgg()])) query = attr.ib(default="", validator=instance_of(str)) refId = attr.ib(default="", validator=instance_of(str)) + timeField = attr.ib(default="@timestamp", validator=instance_of(str)) def _map_bucket_aggs(self, f): return attr.evolve(self, bucketAggs=list(map(f, self.bucketAggs))) @@ -362,6 +364,7 @@ def to_json_data(self): 'metrics': self.metricAggs, 'query': self.query, 'refId': self.refId, + 'timeField': self.timeField, } From b8e20f99b2becdd55cc54603fdd846a7650f1648 Mon Sep 17 00:00:00 2001 From: dafna-starkware <61111179+dafna-starkware@users.noreply.github.com> Date: Wed, 31 Mar 2021 10:31:50 +0300 Subject: [PATCH 218/403] Add nameFilter field for the AlertList panel. (#338) * Add nameFilter field for the AlertList panel. * Add a documentation for the AlertList class. * Update core.py Add support for grid position to AlertList panel * Fix linting error * Improve AlertList documentation and add a dashboardTags field. * Fix linting errors * Use iterable_validator field. Co-authored-by: JamesGibo --- CHANGELOG.rst | 3 +++ grafanalib/core.py | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5ac2fcad..a7503487 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,9 @@ Changelog =========== * Added timeField field for the Elasticsearch target to allow the alert to change its state +* Added nameFilter field for the AlertList panel +* Added dashboardTags field for the AlertList panel + Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index 6af6ee61..2862d86f 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -210,6 +210,7 @@ def to_json_data(self): ALERTLIST_STATE_NO_DATA = 'no_data' ALERTLIST_STATE_EXECUTION_ERROR = 'execution_error' ALERTLIST_STATE_ALERTING = 'alerting' +ALERTLIST_STATE_PENDING = 'pending' # Display Sort Order SORT_ASC = 1 @@ -1469,12 +1470,44 @@ def to_json_data(self): @attr.s class AlertList(object): - """Generates the AlertList Panel.""" + """Generates the AlertList Panel. + :param dashboardTags: A list of tags (strings) for the panel. + :param description: Panel description, supports markdown and links. + :param id: panel id + :param limit: Max number of alerts that can be displayed in the list. + :param nameFilter: Show only alerts that contain nameFilter in their name. + :param onlyAlertsOnDashboard: If true, shows only alerts from the current dashboard. + :param links: Additional web links to be presented in the panel. A list of instantiation of + DataLink objects. + :param show: Show the current alert list (ALERTLIST_SHOW_CURRENT) or only the alerts that were + changed (ALERTLIST_SHOW_CHANGES). + :param sortOrder: Defines the sorting order of the alerts. Gets one of the following values as + input: SORT_ASC, SORT_DESC and SORT_IMPORTANCE. + :param span: Defines the number of spans that will be used for the panel. + :param stateFilter: Show alerts with statuses from the stateFilter list. The list can contain a + subset of the following statuses: + [ALERTLIST_STATE_ALERTING, ALERTLIST_STATE_OK, ALERTLIST_STATE_NO_DATA, + ALERTLIST_STATE_PAUSED, ALERTLIST_STATE_EXECUTION_ERROR, ALERTLIST_STATE_PENDING]. + An empty list means all alerts. + :param title: The panel title. + :param transparent: If true, display the panel without a background. + """ + + dashboardTags = attr.ib( + default=attr.Factory(list), + validator=attr.validators.deep_iterable( + member_validator=attr.validators.instance_of(str), + iterable_validator=attr.validators.instance_of(list))) description = attr.ib(default="") + gridPos = attr.ib(default=None) id = attr.ib(default=None) limit = attr.ib(default=DEFAULT_LIMIT) - links = attr.ib(default=attr.Factory(list)) + links = attr.ib( + default=attr.Factory(list), + validator=attr.validators.deep_iterable( + member_validator=attr.validators.instance_of(DataLink), + iterable_validator=attr.validators.instance_of(list))) onlyAlertsOnDashboard = attr.ib(default=True, validator=instance_of(bool)) show = attr.ib(default=ALERTLIST_SHOW_CURRENT) sortOrder = attr.ib(default=SORT_ASC, validator=in_([1, 2, 3])) @@ -1485,10 +1518,13 @@ class AlertList(object): def to_json_data(self): return { + 'dashboardTags': self.dashboardTags, 'description': self.description, + 'gridPos': self.gridPos, 'id': self.id, 'limit': self.limit, 'links': self.links, + 'nameFilter': self.nameFilter, 'onlyAlertsOnDashboard': self.onlyAlertsOnDashboard, 'show': self.show, 'sortOrder': self.sortOrder, From ba5a7e9b2c088c3d111a5facffe92efaa73b6ab0 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Wed, 7 Apr 2021 12:00:26 +0100 Subject: [PATCH 219/403] Prep for v0.5.11 release (#341) * Prep for v0.5.11 release * Add list of contributors --- CHANGELOG.rst | 9 ++------- setup.py | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a7503487..99bf5ba1 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,19 +2,14 @@ Changelog ========= -0.x.x (TBD) +0.5.1 (2021-04-06) =========== * Added timeField field for the Elasticsearch target to allow the alert to change its state * Added nameFilter field for the AlertList panel * Added dashboardTags field for the AlertList panel - -Changes -------- - -* ... - +Thanks a lot for your contributions to this release, @dafna-starkware 0.5.10 (2021-03-21) ================== diff --git a/setup.py b/setup.py index 4e773f6a..49b63c17 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.10', + version='0.5.11', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 5a8df1a8c197e31208bb713e2d098e773f593550 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Wed, 7 Apr 2021 13:44:03 +0100 Subject: [PATCH 220/403] Add warning about Threshold ordering and fix docstring rendering (#342) --- CHANGELOG.rst | 12 +++++++++ grafanalib/core.py | 66 ++++++++++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 99bf5ba1..f112c9ff 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,18 @@ Changelog ========= +x.x.x (TBD) +================== + +* Added ... + + +Changes +------- + +* Changed ... + + 0.5.1 (2021-04-06) =========== diff --git a/grafanalib/core.py b/grafanalib/core.py index 2862d86f..d933f8fb 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -544,12 +544,12 @@ def _balance_panels(panels): class GridPos(object): """GridPos describes the panel size and position in grid coordinates. - :param h: height of the panel, grid height units each represents - 30 pixels - :param w: width of the panel 1-24 (the width of the dashboard - is divided into 24 columns) - :param x: x cordinate of the panel, in same unit as w - :param y: y cordinate of the panel, in same unit as h + :param h: height of the panel, grid height units each represents + 30 pixels + :param w: width of the panel 1-24 (the width of the dashboard + is divided into 24 columns) + :param x: x cordinate of the panel, in same unit as w + :param y: y cordinate of the panel, in same unit as h """ h = attr.ib() @@ -653,10 +653,10 @@ def to_json_data(self): class ExternalLink(object): """ExternalLink creates a top-level link attached to a dashboard. - :param url: the URL to link to - :param title: the text of the link - :param keepTime: if true, the URL params for the dashboard's - current time period are appended + :param url: the URL to link to + :param title: the text of the link + :param keepTime: if true, the URL params for the dashboard's + current time period are appended """ uri = attr.ib() title = attr.ib() @@ -679,24 +679,24 @@ class Template(object): """Template create a new 'variable' for the dashboard, defines the variable name, human name, query to fetch the values and the default value. - :param default: the default value for the variable - :param dataSource: where to fetch the values for the variable from - :param label: the variable's human label - :param name: the variable's name - :param query: the query users to fetch the valid values of the variable - :param refresh: Controls when to update values in the dropdown - :param allValue: specify a custom all value with regex, - globs or lucene syntax. - :param includeAll: Add a special All option whose value includes - all options. - :param regex: Regex to filter or capture specific parts of the names - return by your data source query. - :param multi: If enabled, the variable will support the selection of - multiple options at the same time. - :param type: The template type, can be one of: query (default), - interval, datasource, custom, constant, adhoc. - :param hide: Hide this variable in the dashboard, can be one of: - SHOW (default), HIDE_LABEL, HIDE_VARIABLE + :param default: the default value for the variable + :param dataSource: where to fetch the values for the variable from + :param label: the variable's human label + :param name: the variable's name + :param query: the query users to fetch the valid values of the variable + :param refresh: Controls when to update values in the dropdown + :param allValue: specify a custom all value with regex, + globs or lucene syntax. + :param includeAll: Add a special All option whose value includes + all options. + :param regex: Regex to filter or capture specific parts of the names + return by your data source query. + :param multi: If enabled, the variable will support the selection of + multiple options at the same time. + :param type: The template type, can be one of: query (default), + interval, datasource, custom, constant, adhoc. + :param hide: Hide this variable in the dashboard, can be one of: + SHOW (default), HIDE_LABEL, HIDE_VARIABLE """ name = attr.ib() @@ -804,6 +804,7 @@ def to_json_data(self): class TimePicker(object): """ Time Picker + :param refreshIntervals: dashboard auto-refresh interval options :param timeOptions: dashboard time range options :param hidden: hide the time picker from dashboard @@ -1103,6 +1104,7 @@ def to_json_data(self): class Panel(object): """ Generic panel for shared defaults + :param cacheTimeout: metric query result cache ttl :param dataSource: Grafana datasource name :param description: optional panel description @@ -1618,6 +1620,7 @@ def to_json_data(self): class StatMapping(object): """ Generates json structure for the value mapping for the Stat panel: + :param text: Sting that will replace input value :param value: Value to be replaced :param startValue: When using a range, the start value of the range @@ -1649,6 +1652,7 @@ def to_json_data(self): class StatValueMapping(object): """ Generates json structure for the value mappings for the StatPanel: + :param text: Sting that will replace input value :param value: Value to be replaced :param id: panel id @@ -1666,6 +1670,7 @@ def to_json_data(self): class StatRangeMapping(object): """ Generates json structure for the value mappings for the StatPanel: + :param text: Sting that will replace input value :param startValue: When using a range, the start value of the range :param endValue: When using a range, the end value of the range @@ -2731,7 +2736,10 @@ class Threshold(object): :param op: EVAL_LT for less than or EVAL_GT for greater than to indicate what the threshold applies to. :param yaxis: Choose left or right for panels - Example: + Care must be taken in the order in which the Threshold objects are specified, + Grafana expects the value to increase. + + Example:: thresholds = [ Threshold('green', 0, 0.0), Threshold('red', 1, 80.0) From fcf2dccf7f36e1e397973fdee1eb28b7592c2b70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Apr 2021 08:14:27 +0100 Subject: [PATCH 221/403] Bump sphinx-rtd-theme from 0.5.1 to 0.5.2 (#344) Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 0.5.1 to 0.5.2. - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/0.5.1...0.5.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 64d3a009..2a21da4a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx == 3.5.3 -sphinx_rtd_theme == 0.5.1 +sphinx_rtd_theme == 0.5.2 From 83c42c1e7661da1f06c1792bbe300c112d15791a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Apr 2021 09:39:34 +0100 Subject: [PATCH 222/403] Bump sphinx from 3.5.3 to 3.5.4 (#345) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.5.3 to 3.5.4. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/commits/v3.5.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: JamesGibo --- docs/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 2a21da4a..08d89b23 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.5.3 -sphinx_rtd_theme == 0.5.2 +sphinx == 3.5.4 +sphinx_rtd_theme == 0.5.2 \ No newline at end of file From 46023884fc02296c9cafc4281b2f4872cbf770ae Mon Sep 17 00:00:00 2001 From: Tom Kaminski Date: Fri, 16 Apr 2021 02:46:25 -0500 Subject: [PATCH 223/403] Set target datasource as None instead of empty string (#348) * Set target datasource as None instead of empty string * Update CHANGELOG.rst Co-authored-by: Tom Kaminski Co-authored-by: JamesGibo --- CHANGELOG.rst | 5 +++-- grafanalib/core.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f112c9ff..89ee2d1a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,13 +8,14 @@ x.x.x (TBD) * Added ... + Changes ------- -* Changed ... +* Fix default target datasource to work with newer versions of Grafana -0.5.1 (2021-04-06) +0.5.10 (2021-04-06) =========== * Added timeField field for the Elasticsearch target to allow the alert to change its state diff --git a/grafanalib/core.py b/grafanalib/core.py index d933f8fb..df808974 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -390,7 +390,7 @@ class Target(object): step = attr.ib(default=DEFAULT_STEP) target = attr.ib(default="") instant = attr.ib(validator=instance_of(bool), default=False) - datasource = attr.ib(default="") + datasource = attr.ib(default=None) def to_json_data(self): return { From ef10858110d8a612050013a89649380141fb461f Mon Sep 17 00:00:00 2001 From: Haijing Wei <5095870+holgercn@users.noreply.github.com> Date: Fri, 16 Apr 2021 10:00:03 +0200 Subject: [PATCH 224/403] bugfix load_module for python version 2.x, 3.4 and 3.5 (#347) * add load_module for python version 2.x, 3.4 and 3.5 * Update CHANGELOG.rst * Fix tests * Update _gen.py * bugfix param doc showLegendValues Co-authored-by: JamesGibo --- CHANGELOG.rst | 2 ++ grafanalib/_gen.py | 7 +++++++ grafanalib/core.py | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 89ee2d1a..f9469bf2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,8 @@ x.x.x (TBD) Changes ------- + +* bugfix load_dashboard add support for old python version 2.x, 3.3 and 3.4 * Fix default target datasource to work with newer versions of Grafana diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index 857d94ac..0175a2a6 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -25,9 +25,16 @@ def load_dashboard(path): spec = importlib.util.spec_from_file_location('dashboard', path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) + elif sys.version_info[0] == 3 and (sys.version_info[1] >= 3 or sys.version_info[1] <= 4): + from importlib.machinery import SourceFileLoader + module = SourceFileLoader("dashboard", path).load_module() + elif sys.version_info[0] == 2: + import imp + module = imp.load_source('dashboard', path) else: import importlib module = importlib.load_source('dashboard', path) + marker = object() dashboard = getattr(module, 'dashboard', marker) if dashboard is marker: diff --git a/grafanalib/core.py b/grafanalib/core.py index df808974..98dbaaa1 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2593,7 +2593,7 @@ class PieChart(Panel): :param id: panel id :param pieType: defines the shape of the pie chart (pie or donut) :param showLegend: defines if the legend should be shown - :param showLegend: defines if the legend should show values + :param showLegendValues: defines if the legend should show values :param legendType: defines where the legend position :param links: additional web links :param span: defines the number of spans that will be used for panel From e965fe916572934eaef55e9859a4bd3f10ef06e1 Mon Sep 17 00:00:00 2001 From: rcowham Date: Fri, 16 Apr 2021 09:12:52 +0100 Subject: [PATCH 225/403] Table driven example with simple upload script (#346) * Table driven example * Fix flake8 warnings * Update CHANGELOG * Rename example script to avoid breaking tests Co-authored-by: JamesGibo --- CHANGELOG.rst | 9 +- .../tests/examples/table-example-dashboard.py | 108 ++++++++++++++++++ .../examples/upload_grafana_dashboard.sh | 57 +++++++++ 3 files changed, 169 insertions(+), 5 deletions(-) create mode 100755 grafanalib/tests/examples/table-example-dashboard.py create mode 100755 grafanalib/tests/examples/upload_grafana_dashboard.sh diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f9469bf2..4d42af2c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,17 +8,16 @@ x.x.x (TBD) * Added ... - Changes ------- - * bugfix load_dashboard add support for old python version 2.x, 3.3 and 3.4 * Fix default target datasource to work with newer versions of Grafana +* Added table-driven example dashboard and upload script -0.5.10 (2021-04-06) -=========== +0.5.11 (2021-04-06) +=================== * Added timeField field for the Elasticsearch target to allow the alert to change its state * Added nameFilter field for the AlertList panel @@ -27,7 +26,7 @@ Changes Thanks a lot for your contributions to this release, @dafna-starkware 0.5.10 (2021-03-21) -================== +=================== * Added Logs panel (https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/) * Added Cloudwatch metrics datasource (https://grafana.com/docs/grafana/latest/datasources/cloudwatch/) diff --git a/grafanalib/tests/examples/table-example-dashboard.py b/grafanalib/tests/examples/table-example-dashboard.py new file mode 100755 index 00000000..d067476c --- /dev/null +++ b/grafanalib/tests/examples/table-example-dashboard.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +NAME: + table-example-dashboard.py + +DESCRIPTION: + This script creates Grafana dashboards using Grafanalib, and a static table + which defines metrics/dashboards. + + The resulting dashboard can be easily uploaded to Grafana with associated script: + + upload_grafana_dashboard.sh + +USAGE: + Create and upload the dashboard: + + ./table-example-dashboard.py --title "My python dashboard" > dash.json + ./upload_grafana_dashboard.sh dash.json + +""" + +import textwrap +import argparse +import sys +import io +import grafanalib.core as G +from grafanalib._gen import write_dashboard + +DEFAULT_TITLE = "Python Example Dashboard" + +# Simple example of table drive - good to enhance with Grid position, Legend etc. +metrics = [ + {'section': 'Monitor Tracking'}, + {'row': 1}, + {'title': 'Monitor Processes (by cmd)', + 'expr': ['monitor_by_cmd{serverid="$serverid"}', + 'sum(monitor_by_cmd{serverid="$serverid"})']}, + {'title': 'Monitor Processes (by user)', + 'expr': ['monitor_by_user{serverid="$serverid"}', + 'sum(monitor_by_user{serverid="$serverid"})']}, +] + + +class CreateDashboard(): + "See module doc string for details" + + def __init__(self, *args, **kwargs): + self.parse_args(__doc__, args) + + def parse_args(self, doc, args): + "Common parsing and setting up of args" + desc = textwrap.dedent(doc) + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=desc) + parser.add_argument('-t', '--title', default=DEFAULT_TITLE, + help="Dashboard title. Default: " + DEFAULT_TITLE) + self.options = parser.parse_args(args=args) + + def run(self): + templateList = [G.Template(default="", + dataSource="default", + name="serverid", + label="ServerID", + query="label_values(serverid)")] + + dashboard = G.Dashboard(title=self.options.title, + templating=G.Templating(list=templateList)) + + # Simple table processing - could be enhanced to use GridPos etc. + for metric in metrics: + if 'section' in metric: + dashboard.rows.append(G.Row(title=metric['section'], showTitle=True)) + continue + if 'row' in metric: + dashboard.rows.append(G.Row(title='', showTitle=False)) + continue + graph = G.Graph(title=metric['title'], + dataSource='default', + maxDataPoints=1000, + legend=G.Legend(show=True, alignAsTable=True, + min=True, max=True, avg=True, current=True, total=True, + sort='max', sortDesc=True), + yAxes=G.single_y_axis()) + ref_id = 'A' + for texp in metric['expr']: + graph.targets.append(G.Target(expr=texp, + refId=ref_id)) + ref_id = chr(ord(ref_id) + 1) + dashboard.rows[-1].panels.append(graph) + + # Auto-number panels - returns new dashboard + dashboard = dashboard.auto_panel_ids() + + s = io.StringIO() + write_dashboard(dashboard, s) + print("""{ + "dashboard": %s + } + """ % s.getvalue()) + + +if __name__ == '__main__': + """ Main Program""" + obj = CreateDashboard(*sys.argv[1:]) + obj.run() diff --git a/grafanalib/tests/examples/upload_grafana_dashboard.sh b/grafanalib/tests/examples/upload_grafana_dashboard.sh new file mode 100755 index 00000000..4d95a2f4 --- /dev/null +++ b/grafanalib/tests/examples/upload_grafana_dashboard.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +show_help_info () { +echo -e "\n\tERROR: $1" + +cat < + +Example: + + ./upload_grafana_dashboard.sh dash.json + +HELPINFO +} + +function msg () { echo -e "$*"; } +function bail () { msg "\nError: ${1:-Unknown Error}\n"; exit ${2:-1}; } + +# ------------------------------------------------------------------------- +if [ -z "$1" ];then + show_help_info "No dashboard parameter received" + exit 1 +fi + +GRAFANA_API_KEY=${GRAFANA_API_KEY:-Unset} +if [[ $GRAFANA_API_KEY == Unset ]]; then + echo -e "\\nError: GRAFANA_API_KEY environment variable not define.\\n" + exit 1 +fi +GRAFANA_SERVER=${GRAFANA_SERVER:-Unset} +if [[ $GRAFANA_SERVER == Unset ]]; then + echo -e "\\nError: GRAFANA_SERVER environment variable not define.\\n" + exit 1 +fi +logfile="grafana_upload.log" + +# Get path/file parm +DASHBOARD=$1 + +# Pull through jq to validate json +payload="$(jq . ${DASHBOARD}) >> $logfile" + +# Upload the JSON to Grafana +curl -X POST \ + -H 'Content-Type: application/json' \ + -d "${payload}" \ + "http://api_key:$GRAFANA_API_KEY@$GRAFANA_SERVER/api/dashboards/db" -w "\n" | tee -a "$logfile" From c1a5f1d9225a351ee8f4abebbc014057ef579e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez?= Date: Sun, 18 Apr 2021 13:59:35 +0200 Subject: [PATCH 226/403] Remove re-defined maxDataPoints from multiple panels (#349) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add maxDataPoints to the Stats panel Signed-off-by: Daniel González Lopes * Add to changelog Signed-off-by: Daniel González Lopes * Remove re-defined maxDataPoints field Signed-off-by: Daniel González Lopes * Update CHANGELOG.rst Co-authored-by: JamesGibo --- CHANGELOG.rst | 1 + grafanalib/core.py | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4d42af2c..e24e4869 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Changes * bugfix load_dashboard add support for old python version 2.x, 3.3 and 3.4 * Fix default target datasource to work with newer versions of Grafana * Added table-driven example dashboard and upload script +* Removed re-defined maxDataPoints field from multiple panels 0.5.11 (2021-04-06) diff --git a/grafanalib/core.py b/grafanalib/core.py index 98dbaaa1..77df68a7 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1722,8 +1722,6 @@ class SingleStat(Panel): Additional info can be found in docs: https://grafana.com/docs/grafana/latest/features/panels/singlestat/#value-to-text-mapping :param mappingTypes: the list of available mapping types for panel - :param maxDataPoints: maximum metric query results, - that will be used for rendering :param minSpan: minimum span number :param nullText: defines what to show if metric query result is undefined :param nullPointMode: defines how to render undefined values @@ -1762,7 +1760,6 @@ class SingleStat(Panel): MAPPING_RANGE_TO_TEXT, ]), ) - maxDataPoints = attr.ib(default=100) minSpan = attr.ib(default=None) nullText = attr.ib(default=None) nullPointMode = attr.ib(default='connected') @@ -1794,7 +1791,6 @@ def to_json_data(self): 'hideTimeOverride': self.hideTimeOverride, 'mappingType': self.mappingType, 'mappingTypes': self.mappingTypes, - 'maxDataPoints': self.maxDataPoints, 'minSpan': self.minSpan, 'nullPointMode': self.nullPointMode, 'nullText': self.nullText, @@ -2096,8 +2092,6 @@ class BarGauge(Panel): :param limit: limit of number of values to show when not Calculating :param links: additional web links :param max: maximum value of the gauge - :param maxDataPoints: maximum metric query results, - that will be used for rendering :param min: minimum value of the gauge :param minSpan: minimum span number :param orientation: orientation of the bar gauge @@ -2135,7 +2129,6 @@ class BarGauge(Panel): label = attr.ib(default=None) limit = attr.ib(default=None) max = attr.ib(default=100) - maxDataPoints = attr.ib(default=100) min = attr.ib(default=0) minSpan = attr.ib(default=None) orientation = attr.ib( @@ -2162,7 +2155,6 @@ def to_json_data(self): 'cacheTimeout': self.cacheTimeout, 'hideTimeOverride': self.hideTimeOverride, 'interval': self.interval, - 'maxDataPoints': self.maxDataPoints, 'minSpan': self.minSpan, 'options': { 'displayMode': self.displayMode, @@ -2212,8 +2204,6 @@ class GaugePanel(Panel): :param limit: limit of number of values to show when not Calculating :param links: additional web links :param max: maximum value of the gauge - :param maxDataPoints: maximum metric query results, - that will be used for rendering :param min: minimum value of the gauge :param minSpan: minimum span number :param rangeMaps: the list of value to text mappings @@ -2240,7 +2230,6 @@ class GaugePanel(Panel): label = attr.ib(default=None) limit = attr.ib(default=None) max = attr.ib(default=100) - maxDataPoints = attr.ib(default=100) min = attr.ib(default=0) minSpan = attr.ib(default=None) rangeMaps = attr.ib(default=attr.Factory(list)) @@ -2263,7 +2252,6 @@ def to_json_data(self): 'cacheTimeout': self.cacheTimeout, 'hideTimeOverride': self.hideTimeOverride, 'interval': self.interval, - 'maxDataPoints': self.maxDataPoints, 'minSpan': self.minSpan, 'options': { 'fieldOptions': { From 5700053a19d3333969888b9054ba3939421893b2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Apr 2021 07:50:05 +0100 Subject: [PATCH 227/403] Bump lycheeverse/lychee-action from v1.0.6 to v1.0.7 (#353) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from v1.0.6 to v1.0.7. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.0.6...842ca57c1c592f9d6c6e57795537f581d9c5a278) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 9b0ac71c..10680596 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.0.6 + uses: lycheeverse/lychee-action@v1.0.7 with: args: --verbose **/*.html - name: Fail if there were link errors From 058a93483c2e6934f8fdfa4d6b36ca260fabaaf5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Apr 2021 07:50:18 +0100 Subject: [PATCH 228/403] Bump actions/setup-python from v2.2.1 to v2.2.2 (#352) Bumps [actions/setup-python](https://github.com/actions/setup-python) from v2.2.1 to v2.2.2. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.2.1...dc73133d4da04e56a135ae2246682783cc7c7cb6) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 10680596..57d617d0 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.3.4 - name: Set up Python - uses: actions/setup-python@v2.2.1 + uses: actions/setup-python@v2.2.2 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index bcdf3528..3cd5db36 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.3.4 - name: Set up Python 3.7 - uses: actions/setup-python@v2.2.1 + uses: actions/setup-python@v2.2.2 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 93d471f4..51fe89ee 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2.3.4 - name: Set up Python - uses: actions/setup-python@v2.2.1 + uses: actions/setup-python@v2.2.2 with: python-version: ${{ matrix.python }} - name: Run tests From 5e5e8c1f3ee4b8b714aa6276457651ec3b0ae6f3 Mon Sep 17 00:00:00 2001 From: dafna-starkware <61111179+dafna-starkware@users.noreply.github.com> Date: Mon, 19 Apr 2021 16:43:43 +0300 Subject: [PATCH 229/403] Fix name filter field. (#354) --- CHANGELOG.rst | 3 ++- grafanalib/core.py | 6 ++++-- grafanalib/tests/test_core.py | 27 +++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e24e4869..214aa708 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,10 +11,11 @@ x.x.x (TBD) Changes ------- -* bugfix load_dashboard add support for old python version 2.x, 3.3 and 3.4 +* bugfix load_dashboard add support for old python version 2.x, 3.3 and 3.4 * Fix default target datasource to work with newer versions of Grafana * Added table-driven example dashboard and upload script * Removed re-defined maxDataPoints field from multiple panels +* Fix the AlertList class and add a test for it 0.5.11 (2021-04-06) diff --git a/grafanalib/core.py b/grafanalib/core.py index 77df68a7..b24dd0b3 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1476,6 +1476,7 @@ class AlertList(object): :param dashboardTags: A list of tags (strings) for the panel. :param description: Panel description, supports markdown and links. + :param gridPos: describes the panel size and position in grid coordinates. :param id: panel id :param limit: Max number of alerts that can be displayed in the list. :param nameFilter: Show only alerts that contain nameFilter in their name. @@ -1501,8 +1502,8 @@ class AlertList(object): validator=attr.validators.deep_iterable( member_validator=attr.validators.instance_of(str), iterable_validator=attr.validators.instance_of(list))) - description = attr.ib(default="") - gridPos = attr.ib(default=None) + description = attr.ib(default="", validator=instance_of(str)) + gridPos = attr.ib(default=None, validator=instance_of(GridPos)) id = attr.ib(default=None) limit = attr.ib(default=DEFAULT_LIMIT) links = attr.ib( @@ -1510,6 +1511,7 @@ class AlertList(object): validator=attr.validators.deep_iterable( member_validator=attr.validators.instance_of(DataLink), iterable_validator=attr.validators.instance_of(list))) + nameFilter = attr.ib(default="", validator=instance_of(str)) onlyAlertsOnDashboard = attr.ib(default=True, validator=instance_of(bool)) show = attr.ib(default=ALERTLIST_SHOW_CURRENT) sortOrder = attr.ib(default=SORT_ASC, validator=in_([1, 2, 3])) diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 14ea54eb..e72ee284 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -1,8 +1,21 @@ """Tests for core.""" +import random import grafanalib.core as G +def dummy_grid_pos() -> G.GridPos: + return G.GridPos(h=1, w=2, x=3, y=4) + + +def dummy_data_link(): + return G.DataLink( + title='dummy title', + linkUrl='https://www.dummy-link-url.com', + isNewTab=True + ) + + def test_template_defaults(): t = G.Template( name='test', @@ -233,3 +246,17 @@ def test_graph_threshold_custom(): assert data['op'] == G.EVAL_GT assert data['fillColor'] == color assert data['lineColor'] == G.RED + + +def test_alert_list(): + alert_list = G.AlertList( + dashboardTags=['dummy tag'], + description='dummy description', + gridPos=dummy_grid_pos(), + id=random.randint(1, 10), + links=[dummy_data_link(), dummy_data_link()], + nameFilter='dummy name filter', + stateFilter=[G.ALERTLIST_STATE_ALERTING, G.ALERTLIST_STATE_OK], + title='dummy title' + ) + alert_list.to_json_data() From 3e9542cfa868f64a18ae7648801c049711f50335 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Noga=C5=9B?= Date: Fri, 23 Apr 2021 16:43:28 +0200 Subject: [PATCH 230/403] Added 'hide' parameter to CloudwatchMetricsTarget class (#355) * Added hide parameter to CloudwatchMetricsTarget class Co-authored-by: JamesGibo --- CHANGELOG.rst | 1 + grafanalib/cloudwatch.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 214aa708..7c486146 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,7 @@ Changes * Added table-driven example dashboard and upload script * Removed re-defined maxDataPoints field from multiple panels * Fix the AlertList class and add a test for it +* Added hide parameter to CloudwatchMetricsTarget class 0.5.11 (2021-04-06) diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py index 15f059d9..60b51d81 100644 --- a/grafanalib/cloudwatch.py +++ b/grafanalib/cloudwatch.py @@ -27,6 +27,7 @@ class CloudwatchMetricsTarget(object): :param refId: target reference id :param region: Cloudwatch region :param statistics: Cloudwatch mathematic statistic + :param hide: controls if given metric is displayed on visualization """ alias = attr.ib(default="") dimensions = attr.ib(default={}, validator=instance_of(dict)) @@ -39,6 +40,7 @@ class CloudwatchMetricsTarget(object): refId = attr.ib(default="") region = attr.ib(default="default") statistics = attr.ib(default=["Average"], validator=instance_of(list)) + hide = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): @@ -53,5 +55,6 @@ def to_json_data(self): "period": self.period, "refId": self.refId, "region": self.region, - "statistics": self.statistics + "statistics": self.statistics, + "hide": self.hide, } From c965f3f93c2d073b93887e56924d025fea7eb189 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Sat, 24 Apr 2021 10:43:20 +0100 Subject: [PATCH 231/403] Prep for v0.5.12 release (#356) * Prep for v0.5.12 release * Change date --- CHANGELOG.rst | 10 +++++----- setup.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7c486146..2b0eb4c7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,21 +2,21 @@ Changelog ========= -x.x.x (TBD) +0.5.12 (2021-04-24) ================== -* Added ... - +* Added hide parameter to CloudwatchMetricsTarget class +* Added table-driven example dashboard and upload script Changes ------- * bugfix load_dashboard add support for old python version 2.x, 3.3 and 3.4 * Fix default target datasource to work with newer versions of Grafana -* Added table-driven example dashboard and upload script * Removed re-defined maxDataPoints field from multiple panels * Fix the AlertList class and add a test for it -* Added hide parameter to CloudwatchMetricsTarget class + +Thanks to all those who have contributed to this release. 0.5.11 (2021-04-06) diff --git a/setup.py b/setup.py index 49b63c17..648ac777 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.11', + version='0.5.12', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 7cc8ec3e0738bf0de9466e95d1806b3236da413b Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Sat, 24 Apr 2021 10:49:27 +0100 Subject: [PATCH 232/403] Add placeholder for next release (#357) --- CHANGELOG.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2b0eb4c7..0882bc64 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,16 @@ Changelog ========= +x.x.x (TBD) +================== + +* Added ... + +Changes +------- + +* + 0.5.12 (2021-04-24) ================== From 581e8abc666560991606debd78ca6c31a9062713 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Apr 2021 07:23:02 +0100 Subject: [PATCH 233/403] Bump lycheeverse/lychee-action from v1.0.7 to v1.0.8 (#358) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from v1.0.7 to v1.0.8. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.0.7...f56bc7dad9caaeb809ce24d5bdb2beaa425f66bc) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 57d617d0..b8d6133b 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.0.7 + uses: lycheeverse/lychee-action@v1.0.8 with: args: --verbose **/*.html - name: Fail if there were link errors From e007cfcbfe0c81fe5950abb816fccd99e12bb21a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Noga=C5=9B?= Date: Thu, 29 Apr 2021 10:13:16 +0200 Subject: [PATCH 234/403] Change 'Target' validator to a generic one in 'AlertCondition' (#359) * Accept 'CloudwatchMetricsTarget' as target for 'AlertCondition' --- CHANGELOG.rst | 2 +- grafanalib/core.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0882bc64..a199ad70 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,7 +10,7 @@ x.x.x (TBD) Changes ------- -* +* Bugfix: changed 'target' validator in AlertNotification to accept CloudwatchMetricsTarget 0.5.12 (2021-04-24) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index b24dd0b3..ba7807cc 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -371,6 +371,14 @@ class Repeat(object): maxPerRow = attr.ib(default=None, validator=is_valid_max_per_row) +def is_valid_target(instance, attribute, value): + """ + Check if a given attribute is a valid target + """ + if not hasattr(value, "refId"): + raise ValueError(f"{attribute.name} should have 'refId' attribute") + + @attr.s class Target(object): """ @@ -920,7 +928,7 @@ class AlertCondition(object): :param type: CTYPE_* """ - target = attr.ib(validator=instance_of(Target)) + target = attr.ib(validator=is_valid_target) evaluator = attr.ib(validator=instance_of(Evaluator)) timeRange = attr.ib(validator=instance_of(TimeRange)) operator = attr.ib() From a2686024df94c8d86527de5c81a36978e15085fc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 May 2021 07:33:12 +0100 Subject: [PATCH 235/403] Bump sphinx from 3.5.4 to 4.0.0 (#363) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 3.5.4 to 4.0.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v3.5.4...v4.0.0) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 08d89b23..55fb3ae2 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 3.5.4 +sphinx == 4.0.0 sphinx_rtd_theme == 0.5.2 \ No newline at end of file From ec3205a54116840a84cbf619dc118beba41c5f88 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 May 2021 05:47:47 +0000 Subject: [PATCH 236/403] Bump sphinx from 4.0.0 to 4.0.1 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.0...v4.0.1) Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 55fb3ae2..9d6f1bd2 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.0.0 +sphinx == 4.0.1 sphinx_rtd_theme == 0.5.2 \ No newline at end of file From 5b35cc5f369868db7430fef039c08a002ade222f Mon Sep 17 00:00:00 2001 From: dafna-starkware <61111179+dafna-starkware@users.noreply.github.com> Date: Mon, 17 May 2021 10:47:37 +0300 Subject: [PATCH 237/403] Move alertRuleTag from Graph to Alert. (#360) --- CHANGELOG.rst | 3 ++- grafanalib/core.py | 19 ++++++++++++++----- grafanalib/tests/test_core.py | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a199ad70..7637d046 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,12 +5,13 @@ Changelog x.x.x (TBD) ================== -* Added ... +* Added a test for the Alert class. Changes ------- * Bugfix: changed 'target' validator in AlertNotification to accept CloudwatchMetricsTarget +* Moved the alertRuleTag field from Graph to Alert. 0.5.12 (2021-04-24) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index ba7807cc..abd92fd8 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -958,7 +958,9 @@ def to_json_data(self): @attr.s class Alert(object): - + """ + :param alertRuleTags: Key Value pairs to be sent with Alert notifications. + """ name = attr.ib() message = attr.ib() alertConditions = attr.ib() @@ -968,6 +970,14 @@ class Alert(object): noDataState = attr.ib(default=STATE_NO_DATA) notifications = attr.ib(default=attr.Factory(list)) gracePeriod = attr.ib(default='5m') + alertRuleTags = attr.ib( + default=attr.Factory(dict), + validator=attr.validators.deep_mapping( + key_validator=attr.validators.instance_of(str), + value_validator=attr.validators.instance_of(str), + mapping_validator=attr.validators.instance_of(dict), + ) + ) def to_json_data(self): return { @@ -980,6 +990,7 @@ def to_json_data(self): 'noDataState': self.noDataState, 'notifications': self.notifications, 'for': self.gracePeriod, + 'alertRuleTags': self.alertRuleTags, } @@ -1270,7 +1281,6 @@ class Graph(Panel): Generates Graph panel json structure. :param alert: List of AlertConditions - :param alertRuleTags: Key Value pairs to be sent with Alert notifications :param dataLinks: List of data links hooked to datapoints on the graph :param dataSource: DataSource's name :param minSpan: Minimum width for each panel @@ -1278,7 +1288,6 @@ class Graph(Panel): """ alert = attr.ib(default=None) - alertRuleTags = attr.ib(default=attr.Factory(dict)) alertThreshold = attr.ib(default=True, validator=instance_of(bool)) aliasColors = attr.ib(default=attr.Factory(dict)) bars = attr.ib(default=False, validator=instance_of(bool)) @@ -1353,7 +1362,6 @@ def to_json_data(self): } if self.alert: graphObject['alert'] = self.alert - graphObject['alertRuleTags'] = self.alertRuleTags graphObject['thresholds'] = [] if self.thresholds and self.alert: print("Warning: Graph threshold ignored as Alerts defined") @@ -1511,7 +1519,8 @@ class AlertList(object): member_validator=attr.validators.instance_of(str), iterable_validator=attr.validators.instance_of(list))) description = attr.ib(default="", validator=instance_of(str)) - gridPos = attr.ib(default=None, validator=instance_of(GridPos)) + gridPos = attr.ib( + default=None, validator=attr.validators.optional(attr.validators.instance_of(GridPos))) id = attr.ib(default=None) limit = attr.ib(default=DEFAULT_LIMIT) links = attr.ib( diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index e72ee284..34db064e 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -8,7 +8,7 @@ def dummy_grid_pos() -> G.GridPos: return G.GridPos(h=1, w=2, x=3, y=4) -def dummy_data_link(): +def dummy_data_link() -> G.DataLink: return G.DataLink( title='dummy title', linkUrl='https://www.dummy-link-url.com', @@ -16,6 +16,28 @@ def dummy_data_link(): ) +def dummy_evaluator() -> G.Evaluator: + return G.Evaluator( + type=G.EVAL_GT, + params=42 + ) + + +def dummy_alert_condition() -> G.AlertCondition: + return G.AlertCondition( + target=G.Target(), + evaluator=G.Evaluator( + type=G.EVAL_GT, + params=42), + timeRange=G.TimeRange( + from_time='5m', + to_time='now' + ), + operator=G.OP_AND, + reducerType=G.RTYPE_AVG, + ) + + def test_template_defaults(): t = G.Template( name='test', @@ -260,3 +282,13 @@ def test_alert_list(): title='dummy title' ) alert_list.to_json_data() + + +def test_alert(): + alert = G.Alert( + name='dummy name', + message='dummy message', + alertConditions=dummy_alert_condition(), + alertRuleTags=dict(alert_rul_dummy_key='alert rul dummy value') + ) + alert.to_json_data() From 1cd4d4c0fe84383097caeb46603d09873b5d8008 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Mon, 17 May 2021 09:31:08 +0100 Subject: [PATCH 238/403] Prep v0.5.13 (#365) --- CHANGELOG.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7637d046..723bcd7e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,7 @@ Changelog ========= -x.x.x (TBD) +0.5.13 (2021-05-17) ================== * Added a test for the Alert class. diff --git a/setup.py b/setup.py index 648ac777..749defea 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.12', + version='0.5.13', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From b581a6042f5294125dd72b7bf0aff4cdbbb087e3 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Mon, 17 May 2021 09:40:23 +0100 Subject: [PATCH 239/403] Update changelog (#366) --- CHANGELOG.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 723bcd7e..600c1727 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,16 @@ Changelog ========= +x.x.x (TBD) +================== + +* Added ... + +Changes +------- + +* ... + 0.5.13 (2021-05-17) ================== From 27502b7c0c60f727b47b3f714fbcb0537bd81082 Mon Sep 17 00:00:00 2001 From: Anton Markelov Date: Wed, 19 May 2021 15:48:32 +0200 Subject: [PATCH 240/403] Add transformations field for the Panel class (#367) * add transformations field * fill changelog * use Factory for default value Co-authored-by: JamesGibo * add test for table transformations * fix flake8 errors * move transformations to the Panel class Co-authored-by: JamesGibo --- CHANGELOG.rst | 2 +- grafanalib/core.py | 3 +++ grafanalib/tests/test_core.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 600c1727..4e837083 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,7 +5,7 @@ Changelog x.x.x (TBD) ================== -* Added ... +* Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/transformations/types-options/#transformation-types-and-options) Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index abd92fd8..ee109856 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1142,6 +1142,7 @@ class Panel(object): :param timeFrom: time range that Override relative time :param title: of the panel :param transparent: defines if panel should be transparent + :param transformations: defines transformations applied to the table """ dataSource = attr.ib(default=None) @@ -1164,6 +1165,7 @@ class Panel(object): timeFrom = attr.ib(default=None) timeShift = attr.ib(default=None) transparent = attr.ib(default=False, validator=instance_of(bool)) + transformations = attr.ib(default=attr.Factory(list), validator=instance_of(list)) def _map_panels(self, f): return f(self) @@ -1192,6 +1194,7 @@ def panel_json(self, overrides): 'timeShift': self.timeShift, 'title': self.title, 'transparent': self.transparent, + 'transformations': self.transformations } res.update(overrides) return res diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 34db064e..8b049d92 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -114,6 +114,36 @@ def test_table_styled_columns(): ] +def test_table_transformations(): + t = G.Table( + dataSource='some data source', + targets=[ + G.Target(expr='some expr'), + ], + title='table title', + transformations=[ + { + "id": "seriesToRows", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": True + }, + "indexByName": {}, + "renameByName": { + "Value": "Dummy" + } + } + } + ] + ) + assert len(t.to_json_data()['transformations']) == 2 + assert t.to_json_data()['transformations'][0]["id"] == "seriesToRows" + + def test_stat_no_repeat(): t = G.Stat( title='dummy', From 57fdf5f4dc816b0013eee98f9ae517266675255c Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Wed, 19 May 2021 15:06:39 +0100 Subject: [PATCH 241/403] Add colour overides to pie chart and add missing X Axis class attibutes (#369) * Add colour overides to pie chart and add missing X Axis class attibutes * update changelog --- CHANGELOG.rst | 3 +++ grafanalib/core.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4e837083..8de9906c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,8 +5,11 @@ Changelog x.x.x (TBD) ================== +* Added colour overrides to pie chart panel +* Added missing attributes from xAxis class * Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/transformations/types-options/#transformation-types-and-options) + Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index ee109856..c505ec46 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -443,6 +443,14 @@ def is_valid_xaxis_mode(instance, attribute, value): @attr.s class XAxis(object): + """ + X Axis + + :param mode: Mode of axis can be time, series or histogram + :param name: X axis name + :param value: list of values eg. ["current"] or ["avg"] + :param show: show X axis + """ mode = attr.ib(default='time', validator=is_valid_xaxis_mode) name = attr.ib(default=None) @@ -451,6 +459,9 @@ class XAxis(object): def to_json_data(self): return { + 'mode': self.mode, + 'name': self.name, + 'values': self.values, 'show': self.show, } @@ -2593,6 +2604,8 @@ def to_json_data(self): class PieChart(Panel): """Generates Pie Chart panel json structure Grafana doc on Pie Chart: https://grafana.com/grafana/plugins/grafana-piechart-panel + + :param aliasColors: dictionary of color overrides :param dataSource: Grafana datasource name :param targets: list of metric requests for chosen datasource :param title: panel title @@ -2610,6 +2623,7 @@ class PieChart(Panel): :param transparent: defines if the panel is transparent """ + aliasColors = attr.ib(default=attr.Factory(dict)) format = attr.ib(default='none') legendType = attr.ib(default='Right side') pieType = attr.ib(default='pie') @@ -2620,6 +2634,7 @@ class PieChart(Panel): def to_json_data(self): return self.panel_json( { + 'aliasColors': self.aliasColors, 'format': self.format, 'pieType': self.pieType, 'height': self.height, From 7fb89de5906e6a246be764e42c8cf4a0113c637a Mon Sep 17 00:00:00 2001 From: Anton Markelov Date: Thu, 20 May 2021 16:30:07 +0200 Subject: [PATCH 242/403] Add documentation about generating dashboards from code (#370) --- docs/getting-started.rst | 9 +++++ .../examples/example.upload-dashboard.py | 39 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 grafanalib/tests/examples/example.upload-dashboard.py diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 7d61eee9..2dd6db64 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -42,6 +42,15 @@ If you save the above as ``example.dashboard.py`` (the suffix must be $ generate-dashboard -o frontend.json example.dashboard.py +Generating dashboards from code +=============================== + +Sometimes you may need to generate and upload dashboard directly from Python +code. The following example provides minimal code boilerplate for it: + +.. literalinclude:: ../grafanalib/tests/examples/example.upload-dashboard.py + :language: python + Installation ============ diff --git a/grafanalib/tests/examples/example.upload-dashboard.py b/grafanalib/tests/examples/example.upload-dashboard.py new file mode 100644 index 00000000..73f1bc0e --- /dev/null +++ b/grafanalib/tests/examples/example.upload-dashboard.py @@ -0,0 +1,39 @@ +from grafanalib.core import Dashboard +from grafanalib._gen import DashboardEncoder +import json +import requests +from os import getenv + + +def get_dashboard_json(dashboard): + ''' + get_dashboard_json generates JSON from grafanalib Dashboard object + + :param dashboard - Dashboard() created via grafanalib + ''' + + # grafanalib generates json which need to pack to "dashboard" root element + return json.dumps({"dashboard": dashboard.to_json_data()}, sort_keys=True, indent=2, cls=DashboardEncoder) + + +def upload_to_grafana(json, server, api_key): + ''' + upload_to_grafana tries to upload dashboard to grafana and prints response + + :param json - dashboard json generated by grafanalib + :param server - grafana server name + :param api_key - grafana api key with read and write privileges + ''' + + headers = {'Authorization': f"Bearer {api_key}", 'Content-Type': 'application/json'} + r = requests.post(f"https://{server}/api/dashboards/db", data=json, headers=headers) + # TODO: add error handling + print(f"{r.status_code} - {r.content}") + + +grafana_api_key = getenv("GRAFANA_API_KEY") +grafana_server = getenv("GRAFANA_SERVER") + +my_dashboard = Dashboard(title="My awesome dashboard") +my_dashboard_json = get_dashboard_json(my_dashboard) +upload_to_grafana(my_dashboard_json, grafana_server, grafana_api_key) From c7878a14e208e226a51afa120ed92e941bff6bd8 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Fri, 21 May 2021 12:58:54 +0000 Subject: [PATCH 243/403] Remove Bryan Boreham as maintainer --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 5bb78674..f3f0bcff 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,3 +1,2 @@ -Bryan Boreham (@bboreham) Matt Richter (@matthewmrichter) James Gibson From e73af405dba25916b845dde6ea9a733db3603b62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 May 2021 07:33:26 +0100 Subject: [PATCH 244/403] Bump sphinx from 4.0.1 to 4.0.2 (#372) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.0.1 to 4.0.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.1...v4.0.2) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 9d6f1bd2..5e75e836 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.0.1 +sphinx == 4.0.2 sphinx_rtd_theme == 0.5.2 \ No newline at end of file From c6782a097cc784ad0689db3489c61bade8f96d3e Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 25 May 2021 10:32:22 +0200 Subject: [PATCH 245/403] add myself to maintainers, change format to MAINTAINERS files of Flux (#373) Signed-off-by: Daniel Holbach --- MAINTAINERS | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index f3f0bcff..065b97f2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1,2 +1,17 @@ -Matt Richter (@matthewmrichter) -James Gibson +In alphabetical order: + +The maintainers are generally available in Slack at +https://weave-community.slack.com/ in #grafanalib (https://weave-community.slack.com/archives/C9C9K6T4P) +(obtain an invitation at https://slack.weave.works/). + + +Daniel Holbach, Weaveworks (github: @dholbach, slack: dholbach) +James Gibson, BBC (github: @JamesGibo, slack: James G) +Matt Richter, Validity HQ (github: @matthewmrichter, slack: Matt Richter) + +Retired maintainers: + +- Bryan Boreham +- Jonathan Lange + +Thank you for your involvement, and let us not say "farewell" ... From 09a383eb3a9ee81f1cf8102ae737709b129387fa Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 1 Jun 2021 15:03:28 +0100 Subject: [PATCH 246/403] Add worldmap panel (#374) * Add worldmap panel * Update CHANGELOG.rst Co-authored-by: Daniel Holbach Co-authored-by: Daniel Holbach --- CHANGELOG.rst | 2 +- docs/CONTRIBUTING.rst | 4 ++ grafanalib/core.py | 97 +++++++++++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 12 +++++ 4 files changed, 114 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8de9906c..756b404b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,7 +8,7 @@ x.x.x (TBD) * Added colour overrides to pie chart panel * Added missing attributes from xAxis class * Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/transformations/types-options/#transformation-types-and-options) - +* Added Worldmap panel (https://grafana.com/grafana/plugins/grafana-worldmap-panel/) Changes ------- diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst index f054a63e..449247c9 100644 --- a/docs/CONTRIBUTING.rst +++ b/docs/CONTRIBUTING.rst @@ -45,6 +45,10 @@ Lots of grafanalib is just simple data structures, so we aren't fastidious about However, tests are strongly encouraged for anything with non-trivial logic. Please try to use `hypothesis`_ for your tests. +.. code-block:: console + + $ make all + Gotchas ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index c505ec46..9f33f152 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -89,6 +89,7 @@ def to_json_data(self): STATUSMAP_TYPE = 'flant-statusmap-panel' SVG_TYPE = 'marcuscalidus-svg-panel' PIE_CHART_TYPE = 'grafana-piechart-panel' +WORLD_MAP_TYPE = 'grafana-worldmap-panel' DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -2853,3 +2854,99 @@ def to_json_data(self): 'yaxis': self.yaxis, 'color': self.color, } + + +WORLDMAP_CENTER = ['(0°, 0°)', 'North America', 'Europe', 'West Asia', 'SE Asia', 'Last GeoHash', 'custom'] +WORLDMAP_LOCATION_DATA = ['countries', 'countries_3letter', 'states', 'probes', 'geohash', 'json_endpoint', 'jsonp endpoint', 'json result', 'table'] + + +@attr.s +class Worldmap(Panel): + """Generates Worldmap panel json structure + Grafana doc on Worldmap: https://grafana.com/grafana/plugins/grafana-worldmap-panel/ + + :param aggregation: metric aggregation: min, max, avg, current, total + :param circleMaxSize: Maximum map circle size + :param circleMinSize: Minimum map circle size + :param decimals: Number of decimals to show + :param geoPoint: Name of the geo_point/geohash column. This is used to calculate where the circle should be drawn. + :param locationData: Format of the location data, options in `WORLDMAP_LOCATION_DATA` + :param locationName: Name of the Location Name column. Used to label each circle on the map. If it is empty then the geohash value is used. + :param metric: Name of the metric column. This is used to give the circle a value - this determines how large the circle is. + :param mapCenter: Where to centre the map, default center (0°, 0°). Options: North America, Europe, West Asia, SE Asia, Last GeoHash, custom + :param mapCenterLatitude: If mapCenter=custom set the initial map latitude + :param mapCenterLongitude: If mapCenter=custom set the initial map longitude + :param hideEmpty: Hide series with only nulls + :param hideZero: Hide series with only zeros + :param initialZoom: Initial map zoom + :param jsonUrl: URL for JSON location data if `json_endpoint` or `jsonp endpoint` used + :param jsonpCallback: Callback if `jsonp endpoint` used + :param mouseWheelZoom: Zoom map on scroll of mouse wheel + :param stickyLabels: Sticky map labels + :param thresholds: String of thresholds eg. '0,10,20' + :param thresholdsColors: List of colors to be used in each threshold + :param unitPlural: Units plural + :param unitSingle: Units single + :param unitSingular: Units singular + """ + + circleMaxSize = attr.ib(default=30, validator=instance_of(int)) + circleMinSize = attr.ib(default=2, validator=instance_of(int)) + decimals = attr.ib(default=0, validator=instance_of(int)) + geoPoint = attr.ib(default='geohash', validator=instance_of(str)) + locationData = attr.ib(default='countries', validator=attr.validators.in_(WORLDMAP_LOCATION_DATA)) + locationName = attr.ib(default='') + hideEmpty = attr.ib(default=False, validator=instance_of(bool)) + hideZero = attr.ib(default=False, validator=instance_of(bool)) + initialZoom = attr.ib(default=1, validator=instance_of(int)) + jsonUrl = attr.ib(default='', validator=instance_of(str)) + jsonpCallback = attr.ib(default='', validator=instance_of(str)) + mapCenter = attr.ib(default='(0°, 0°)', validator=attr.validators.in_(WORLDMAP_CENTER)) + mapCenterLatitude = attr.ib(default=0, validator=instance_of(int)) + mapCenterLongitude = attr.ib(default=0, validator=instance_of(int)) + metric = attr.ib(default='Value') + mouseWheelZoom = attr.ib(default=False, validator=instance_of(bool)) + stickyLabels = attr.ib(default=False, validator=instance_of(bool)) + thresholds = attr.ib(default='0,100,150', validator=instance_of(str)) + thresholdColors = attr.ib(default=["#73BF69", "#73BF69", "#FADE2A", "#C4162A"], validator=instance_of(list)) + unitPlural = attr.ib(default='', validator=instance_of(str)) + unitSingle = attr.ib(default='', validator=instance_of(str)) + unitSingular = attr.ib(default='', validator=instance_of(str)) + aggregation = attr.ib(default='total', validator=instance_of(str)) + + def to_json_data(self): + return self.panel_json( + { + 'circleMaxSize': self.circleMaxSize, + 'circleMinSize': self.circleMinSize, + 'colors': self.thresholdColors, + 'decimals': self.decimals, + 'esGeoPoint': self.geoPoint, + 'esMetric': self.metric, + 'locationData': self.locationData, + 'esLocationName': self.locationName, + 'hideEmpty': self.hideEmpty, + 'hideZero': self.hideZero, + 'initialZoom': self.initialZoom, + 'jsonUrl': self.jsonUrl, + 'jsonpCallback': self.jsonpCallback, + 'mapCenter': self.mapCenter, + 'mapCenterLatitude': self.mapCenterLatitude, + 'mapCenterLongitude': self.mapCenterLongitude, + 'mouseWheelZoom': self.mouseWheelZoom, + 'stickyLabels': self.stickyLabels, + 'thresholds': self.thresholds, + 'unitPlural': self.unitPlural, + 'unitSingle': self.unitSingle, + 'unitSingular': self.unitSingular, + 'valueName': self.aggregation, + 'tableQueryOptions': { + 'queryType': 'geohash', + 'geohashField': 'geohash', + 'latitudeField': 'latitude', + 'longitudeField': 'longitude', + 'metricField': 'metric' + }, + 'type': WORLD_MAP_TYPE + } + ) diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 8b049d92..1faacc1d 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -322,3 +322,15 @@ def test_alert(): alertRuleTags=dict(alert_rul_dummy_key='alert rul dummy value') ) alert.to_json_data() + + +def test_worldmap(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + worldmap = G.Worldmap(data_source, targets, title, circleMaxSize=11) + data = worldmap.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title + assert data['circleMaxSize'] == 11 From e58492dca61e50c2dd868c8d121d5e13b48a87b2 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Wed, 2 Jun 2021 10:41:50 +0100 Subject: [PATCH 247/403] Clean up code docs (#378) * Clean up code docs * Fix lint --- grafanalib/core.py | 221 +++++++++------------------------------------ 1 file changed, 42 insertions(+), 179 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 9f33f152..d5701e06 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1578,43 +1578,31 @@ class Stat(Panel): Grafana doc on stat: https://grafana.com/docs/grafana/latest/panels/visualizations/stat-panel/ - :param dataSource: Grafana datasource name - :param targets: list of metric requests for chosen datasource - :param title: panel title - :param textMode: define Grafana will show name or value: keys: 'auto' 'name' 'none' 'value' 'value_and_name' - :param colorMode: defines if Grafana will color panel background: keys "value" "background" - :param graphMode: defines if Grafana will draw graph: keys 'area' 'none' - :param orientation: Stacking direction in case of multiple series or fields: keys 'auto' 'horizontal' 'vertical' :param alignment: defines value & title positioning: keys 'auto' 'centre' - :param description: optional panel description - :param editable: defines if panel is editable via web interfaces - :param format: defines value units - :param height: defines panel height - :param id: panel id + :param colorMode: defines if Grafana will color panel background: keys "value" "background" :param decimals: number of decimals to display - :param interval: defines time interval between metric queries - :param links: additional web links + :param format: defines value units + :param graphMode: defines if Grafana will draw graph: keys 'area' 'none' :param mappings: the list of values to text mappings This should be a list of StatMapping objects https://grafana.com/docs/grafana/latest/panels/field-configuration-options/#value-mapping + :param orientation: Stacking direction in case of multiple series or fields: keys 'auto' 'horizontal' 'vertical' :param reduceCalc: algorithm for reduction to a single value: keys 'mean' 'lastNotNull' 'last' 'first' 'firstNotNull' 'min' 'max' 'sum' 'total' - :param span: defines the number of spans that will be used for panel + :param textMode: define Grafana will show name or value: keys: 'auto' 'name' 'none' 'value' 'value_and_name' :param thresholds: single stat thresholds - :param transparent: defines if the panel should be transparent """ - textMode = attr.ib(default='auto') - colorMode = attr.ib(default='value') - graphMode = attr.ib(default='area') - orientation = attr.ib(default='auto') alignment = attr.ib(default='auto') + colorMode = attr.ib(default='value') + decimals = attr.ib(default=None) format = attr.ib(default='none') + graphMode = attr.ib(default='area') mappings = attr.ib(default=attr.Factory(list)) - span = attr.ib(default=6) - thresholds = attr.ib(default="") + orientation = attr.ib(default='auto') reduceCalc = attr.ib(default='mean', type=str) - decimals = attr.ib(default=None) + textMode = attr.ib(default='auto') + thresholds = attr.ib(default="") def to_json_data(self): return self.panel_json( @@ -1733,30 +1721,19 @@ class SingleStat(Panel): Grafana doc on singlestat: https://grafana.com/docs/grafana/latest/features/panels/singlestat/ - :param dataSource: Grafana datasource name - :param targets: list of metric requests for chosen datasource - :param title: panel title :param cacheTimeout: metric query result cache ttl :param colors: the list of colors that can be used for coloring panel value or background. Additional info on coloring in docs: https://grafana.com/docs/grafana/latest/features/panels/singlestat/#coloring :param colorBackground: defines if grafana will color panel background :param colorValue: defines if grafana will color panel value - :param description: optional panel description :param decimals: override automatic decimal precision for legend/tooltips - :param editable: defines if panel is editable via web interfaces :param format: defines value units :param gauge: draws and additional speedometer-like gauge based - :param height: defines panel height - :param hideTimeOverride: hides time overrides - :param id: panel id - :param interval: defines time interval between metric queries - :param links: additional web links :param mappingType: defines panel mapping type. Additional info can be found in docs: https://grafana.com/docs/grafana/latest/features/panels/singlestat/#value-to-text-mapping :param mappingTypes: the list of available mapping types for panel - :param minSpan: minimum span number :param nullText: defines what to show if metric query result is undefined :param nullPointMode: defines how to render undefined values :param postfix: defines postfix that will be attached to value @@ -1764,17 +1741,14 @@ class SingleStat(Panel): :param prefix: defines prefix that will be attached to value :param prefixFontSize: defines prefix font size :param rangeMaps: the list of value to text mappings - :param span: defines the number of spans that will be used for panel :param sparkline: defines if grafana should draw an additional sparkline. Sparkline grafana documentation: https://grafana.com/docs/grafana/latest/features/panels/singlestat/#spark-lines :param thresholds: single stat thresholds - :param transparent: defines if panel should be transparent :param valueFontSize: defines value font size :param valueName: defines value type. possible values are: min, max, avg, current, total, name, first, delta, range :param valueMaps: the list of value to text mappings - :param timeFrom: time range that Override relative time """ cacheTimeout = attr.ib(default=None) @@ -1785,8 +1759,6 @@ class SingleStat(Panel): format = attr.ib(default='none') gauge = attr.ib(default=attr.Factory(Gauge), validator=instance_of(Gauge)) - hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) - interval = attr.ib(default=None) mappingType = attr.ib(default=MAPPING_TYPE_VALUE_TO_TEXT) mappingTypes = attr.ib( default=attr.Factory(lambda: [ @@ -1821,8 +1793,6 @@ def to_json_data(self): 'decimals': self.decimals, 'format': self.format, 'gauge': self.gauge, - 'interval': self.interval, - 'hideTimeOverride': self.hideTimeOverride, 'mappingType': self.mappingType, 'mappingTypes': self.mappingTypes, 'minSpan': self.minSpan, @@ -2016,31 +1986,17 @@ class Table(Panel): Grafana doc on table: https://grafana.com/docs/grafana/latest/features/panels/table_panel/#table-panel :param columns: table columns for Aggregations view - :param dataSource: Grafana datasource name - :param description: optional panel description - :param editable: defines if panel is editable via web interfaces :param fontSize: defines value font size - :param height: defines panel height - :param hideTimeOverride: hides time overrides - :param id: panel id - :param links: additional web links - :param minSpan: minimum span number :param pageSize: rows per page (None is unlimited) :param scroll: scroll the table instead of displaying in full :param showHeader: show the table header - :param span: defines the number of spans that will be used for panel + :param sort: table sorting :param styles: defines formatting for each column - :param targets: list of metric requests for chosen datasource - :param timeFrom: time range that Override relative time - :param title: panel title :param transform: table style - :param transparent: defines if panel should be transparent """ columns = attr.ib(default=attr.Factory(list)) fontSize = attr.ib(default='100%') - hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) - minSpan = attr.ib(default=None) pageSize = attr.ib(default=None) scroll = attr.ib(default=True, validator=instance_of(bool)) showHeader = attr.ib(default=True, validator=instance_of(bool)) @@ -2109,41 +2065,25 @@ class BarGauge(Panel): """Generates Bar Gauge panel json structure :param allValue: If All values should be shown or a Calculation - :param cacheTimeout: metric query result cache ttl :param calc: Calculation to perform on metrics :param dataLinks: list of data links hooked to datapoints on the graph - :param dataSource: Grafana datasource name :param decimals: override automatic decimal precision for legend/tooltips - :param description: optional panel description :param displayMode: style to display bar gauge in - :param editable: defines if panel is editable via web interfaces :param format: defines value units - :param height: defines panel height - :param hideTimeOverride: hides time overrides - :param id: panel id - :param interval: defines time interval between metric queries :param labels: option to show gauge level labels :param limit: limit of number of values to show when not Calculating - :param links: additional web links :param max: maximum value of the gauge :param min: minimum value of the gauge - :param minSpan: minimum span number :param orientation: orientation of the bar gauge :param rangeMaps: the list of value to text mappings - :param span: defines the number of spans that will be used for panel - :param targets: list of metric requests for chosen datasource :param thresholdLabel: label for gauge. Template Variables: "$__series_namei" "$__field_name" "$__cell_{N} / $__calc" :param thresholdMarkers: option to show marker of level on gauge :param thresholds: single stat thresholds - :param timeFrom: time range that Override relative time - :param title: panel title - :param transparent: defines if panel should be transparent :param valueMaps: the list of value to text mappings """ allValues = attr.ib(default=False, validator=instance_of(bool)) - cacheTimeout = attr.ib(default=None) calc = attr.ib(default=GAUGE_CALC_MEAN) dataLinks = attr.ib(default=attr.Factory(list)) decimals = attr.ib(default=None) @@ -2158,13 +2098,10 @@ class BarGauge(Panel): ), ) format = attr.ib(default='none') - hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) - interval = attr.ib(default=None) label = attr.ib(default=None) limit = attr.ib(default=None) max = attr.ib(default=100) min = attr.ib(default=0) - minSpan = attr.ib(default=None) orientation = attr.ib( default=ORIENTATION_HORIZONTAL, validator=in_([ORIENTATION_HORIZONTAL, ORIENTATION_VERTICAL]), @@ -2186,10 +2123,6 @@ class BarGauge(Panel): def to_json_data(self): return self.panel_json( { - 'cacheTimeout': self.cacheTimeout, - 'hideTimeOverride': self.hideTimeOverride, - 'interval': self.interval, - 'minSpan': self.minSpan, 'options': { 'displayMode': self.displayMode, 'fieldOptions': { @@ -2222,50 +2155,31 @@ class GaugePanel(Panel): """Generates Gauge panel json structure :param allValue: If All values should be shown or a Calculation - :param cacheTimeout: metric query result cache ttl :param calc: Calculation to perform on metrics :param dataLinks: list of data links hooked to datapoints on the graph - :param dataSource: Grafana datasource name :param decimals: override automatic decimal precision for legend/tooltips - :param description: optional panel description - :param editable: defines if panel is editable via web interfaces :param format: defines value units - :param height: defines panel height - :param hideTimeOverride: hides time overrides - :param id: panel id - :param interval: defines time interval between metric queries :param labels: option to show gauge level labels :param limit: limit of number of values to show when not Calculating - :param links: additional web links :param max: maximum value of the gauge :param min: minimum value of the gauge - :param minSpan: minimum span number :param rangeMaps: the list of value to text mappings - :param span: defines the number of spans that will be used for panel - :param targets: list of metric requests for chosen datasource :param thresholdLabel: label for gauge. Template Variables: "$__series_namei" "$__field_name" "$__cell_{N} / $__calc" :param thresholdMarkers: option to show marker of level on gauge :param thresholds: single stat thresholds - :param timeFrom: time range that Override relative time - :param title: panel title - :param transparent: defines if panel should be transparent :param valueMaps: the list of value to text mappings """ allValues = attr.ib(default=False, validator=instance_of(bool)) - cacheTimeout = attr.ib(default=None) calc = attr.ib(default=GAUGE_CALC_MEAN) dataLinks = attr.ib(default=attr.Factory(list)) decimals = attr.ib(default=None) format = attr.ib(default='none') - hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) - interval = attr.ib(default=None) label = attr.ib(default=None) limit = attr.ib(default=None) max = attr.ib(default=100) min = attr.ib(default=0) - minSpan = attr.ib(default=None) rangeMaps = attr.ib(default=attr.Factory(list)) thresholdLabels = attr.ib(default=False, validator=instance_of(bool)) thresholdMarkers = attr.ib(default=True, validator=instance_of(bool)) @@ -2283,10 +2197,6 @@ class GaugePanel(Panel): def to_json_data(self): return self.panel_json( { - 'cacheTimeout': self.cacheTimeout, - 'hideTimeOverride': self.hideTimeOverride, - 'interval': self.interval, - 'minSpan': self.minSpan, 'options': { 'fieldOptions': { 'calcs': [self.calc], @@ -2316,13 +2226,13 @@ def to_json_data(self): class HeatmapColor(object): """A Color object for heatmaps - :param cardColor - :param colorScale - :param colorScheme - :param exponent - :param max - :param min - :param mode + :param cardColor: color + :param colorScale: scale + :param colorScheme: scheme + :param exponent: exponent + :param max: max + :param min: min + :param mode: mode """ # Maybe cardColor should validate to RGBA object, not sure @@ -2350,16 +2260,16 @@ def to_json_data(self): class Heatmap(Panel): """Generates Heatmap panel json structure (https://grafana.com/docs/grafana/latest/features/panels/heatmap/) - :param heatmap + :param heatmap: dict :param cards: A heatmap card object: keys "cardPadding", "cardRound" :param color: Heatmap color object :param dataFormat: 'timeseries' or 'tsbuckets' :param yBucketBound: 'auto', 'upper', 'middle', 'lower' :param reverseYBuckets: boolean - :param xBucketSize - :param xBucketNumber - :param yBucketSize - :param yBucketNumber + :param xBucketSize: Size + :param xBucketNumber: Number + :param yBucketSize: Size + :param yBucketNumber: Number :param highlightCards: boolean :param hideZeroBuckets: boolean :param transparent: defines if the panel should be transparent @@ -2434,14 +2344,14 @@ def to_json_data(self): class StatusmapColor(object): """A Color object for Statusmaps - :param cardColor - :param colorScale - :param colorScheme - :param exponent - :param max - :param min - :param mode - :param thresholds + :param cardColor: colour + :param colorScale: scale + :param colorScheme: scheme + :param exponent: exponent + :param max: max + :param min: min + :param mode: mode + :param thresholds: threshold """ # Maybe cardColor should validate to RGBA object, not sure @@ -2472,27 +2382,15 @@ class Statusmap(Panel): """Generates json structure for the flant-statusmap-panel visualisation plugin (https://grafana.com/grafana/plugins/flant-statusmap-panel/). - :param alert + :param alert: Alert :param cards: A statusmap card object: keys 'cardRound', 'cardMinWidth', 'cardHSpacing', 'cardVSpacing' :param color: A StatusmapColor object - :param dataSource: Name of the datasource to use - :param description: Description of the panel - :param editable - :param id - :param isNew - :param legend - :param links - :param minSpan - :param nullPointMode - :param span - :param targets - :param timeFrom - :param timeShift - :param title: Title of the panel - :param tooltip - :param transparent: Set panel transparency on/off - :param xAxis - :param yAxis + :param isNew: isNew + :param legend: Legend object + :param nullPointMode: null + :param tooltip: Tooltip object + :param xAxis: XAxis object + :param yAxis: YAxis object """ alert = attr.ib(default=None) @@ -2514,7 +2412,6 @@ class Statusmap(Panel): default=attr.Factory(Legend), validator=instance_of(Legend), ) - minSpan = attr.ib(default=None) nullPointMode = attr.ib(default=NULL_AS_ZERO) tooltip = attr.ib( default=attr.Factory(Tooltip), @@ -2550,21 +2447,12 @@ def to_json_data(self): class Svg(Panel): """Generates SVG panel json structure Grafana doc on SVG: https://grafana.com/grafana/plugins/marcuscalidus-svg-panel - :param dataSource: Grafana datasource name - :param targets: list of metric requests for chosen datasource - :param title: panel title - :param description: optional panel description - :param editable: defines if panel is editable via web interfaces + :param format: defines value units :param jsCodeFilePath: path to javascript file to be run on dashboard refresh :param jsCodeInitFilePath: path to javascript file to be run after the first initialization of the SVG - :param height: defines panel height - :param id: panel id - :param interval: defines time interval between metric queries - :param links: additional web links - :param reduceCalc: algorithm for reduction to a single value: + :param reduceCalc: algorithm for reduction to a single value, keys 'mean' 'lastNotNull' 'last' 'first' 'firstNotNull' 'min' 'max' 'sum' 'total' - :param span: defines the number of spans that will be used for panel :param svgFilePath: path to SVG image file to be displayed """ @@ -2607,21 +2495,12 @@ class PieChart(Panel): Grafana doc on Pie Chart: https://grafana.com/grafana/plugins/grafana-piechart-panel :param aliasColors: dictionary of color overrides - :param dataSource: Grafana datasource name - :param targets: list of metric requests for chosen datasource - :param title: panel title - :param description: optional panel description - :param editable: defines if panel is editable via web interfaces :param format: defines value units - :param height: defines panel height - :param id: panel id :param pieType: defines the shape of the pie chart (pie or donut) :param showLegend: defines if the legend should be shown :param showLegendValues: defines if the legend should show values :param legendType: defines where the legend position - :param links: additional web links - :param span: defines the number of spans that will be used for panel - :param transparent: defines if the panel is transparent + :param thresholds: defines thresholds """ aliasColors = attr.ib(default=attr.Factory(dict)) @@ -2659,14 +2538,6 @@ def to_json_data(self): class DashboardList(Panel): """Generates Dashboard list panel json structure Grafana doc on Dashboard list: https://grafana.com/docs/grafana/latest/panels/visualizations/dashboard-list-panel/ - :param title: panel title - :param description: optional panel description - :param editable: defines if panel is editable via web interfaces - :param height: defines panel height - :param id: panel id - :param links: additional web links - :param span: defines the number of spans that will be used for panel - :param transparent: defines if the panel is transparent :param showHeadings: The chosen list selection (Starred, Recently viewed, Search) is shown as a heading :param showSearch: Display dashboards by search query or tags. @@ -2710,14 +2581,6 @@ def to_json_data(self): class Logs(Panel): """Generates Logs panel json structure Grafana doc on Logs panel: https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/ - :param title: panel title - :param description: optional panel description - :param editable: defines if panel is editable via web interfaces - :param height: defines panel height - :param id: panel id - :param links: additional web links - :param span: defines the number of spans that will be used for panel - :param transparent: defines if the panel is transparent :param showLabels: Show or hide the unique labels column, which shows only non-common labels :param showTime: Show or hide the log timestamp column From 3eb848daf6c15a8249be0ac77617030c4db657c3 Mon Sep 17 00:00:00 2001 From: Tenchi Date: Mon, 5 Jul 2021 16:49:35 +0200 Subject: [PATCH 248/403] Add extraJson attribute to the Panel class for overriding the panel with raw JSON (#380) * Add extraJson attribute to the Panel class for overriding the panel with raw JSON * Add docstring line for new extraJson attribute --- CHANGELOG.rst | 1 + grafanalib/core.py | 18 ++++++++++++++++++ grafanalib/tests/test_core.py | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 756b404b..185c5db0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ x.x.x (TBD) * Added missing attributes from xAxis class * Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/transformations/types-options/#transformation-types-and-options) * Added Worldmap panel (https://grafana.com/grafana/plugins/grafana-worldmap-panel/) +* Added ``extraJson`` attribute to the Panel class for overriding the panel with raw JSON Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index d5701e06..84d38651 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1131,6 +1131,20 @@ def to_json_data(self): } +def _deep_update(base_dict, extra_dict): + if extra_dict is None: + return base_dict + + for k, v in extra_dict.items(): + if k in base_dict and hasattr(base_dict[k], "to_json_data"): + base_dict[k] = base_dict[k].to_json_data() + + if k in base_dict and isinstance(base_dict[k], dict): + _deep_update(base_dict[k], v) + else: + base_dict[k] = v + + @attr.s class Panel(object): """ @@ -1155,6 +1169,8 @@ class Panel(object): :param title: of the panel :param transparent: defines if panel should be transparent :param transformations: defines transformations applied to the table + :param extraJson: raw JSON additions or overrides added to the JSON output + of this panel, can be used for using unsupported features """ dataSource = attr.ib(default=None) @@ -1178,6 +1194,7 @@ class Panel(object): timeShift = attr.ib(default=None) transparent = attr.ib(default=False, validator=instance_of(bool)) transformations = attr.ib(default=attr.Factory(list), validator=instance_of(list)) + extraJson = attr.ib(default=None, validator=attr.validators.optional(instance_of(dict))) def _map_panels(self, f): return f(self) @@ -1209,6 +1226,7 @@ def panel_json(self, overrides): 'transformations': self.transformations } res.update(overrides) + _deep_update(res, self.extraJson) return res diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 1faacc1d..a0f1f855 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -232,6 +232,28 @@ def test_graph_panel(): assert 'alert' not in data +def test_panel_extra_json(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + extraJson = { + 'fillGradient': 6, + 'yaxis': {'align': True}, + 'legend': {'avg': True}, + } + graph = G.Graph(data_source, targets, title, extraJson=extraJson) + data = graph.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title + assert 'alert' not in data + assert data['fillGradient'] == 6 + assert data['yaxis']['align'] is True + # Nested non-dict object should also be deep-updated + assert data['legend']['max'] is False + assert data['legend']['avg'] is True + + def test_graph_panel_threshold(): data_source = 'dummy data source' targets = ['dummy_prom_query'] From 7d8612d243179c277c660db846b1912b04bd5d1d Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 6 Jul 2021 07:52:41 +0100 Subject: [PATCH 249/403] Add fill gradient, align and percentage atttibutes (#381) * Add missing fill gradient to Graph panel * Add align to graph panel * Add missing show percentage attribute to Pie chart panel * Fix unit test --- CHANGELOG.rst | 3 +++ grafanalib/core.py | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 185c5db0..ffd4bc02 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,9 @@ x.x.x (TBD) * Added missing attributes from xAxis class * Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/transformations/types-options/#transformation-types-and-options) * Added Worldmap panel (https://grafana.com/grafana/plugins/grafana-worldmap-panel/) +* Added missing fill gradient to Graph panel +* Added missing align to graph panel +* Added missing show percentage attribute to Pie chart panel * Added ``extraJson`` attribute to the Panel class for overriding the panel with raw JSON Changes diff --git a/grafanalib/core.py b/grafanalib/core.py index 84d38651..f31092f0 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -472,7 +472,16 @@ class YAxis(object): """A single Y axis. Grafana graphs have two Y axes: one on the left and one on the right. + + :param decimals: Defines how many decimals are displayed for Y value. (default auto) + :param format: The display unit for the Y value + :param label: The Y axis label. (default “") + :param logBase: The scale to use for the Y value, linear, or logarithmic. (default linear) + :param max: The maximum Y value + :param min: The minimum Y value + :param show: Show or hide the axis """ + decimals = attr.ib(default=None) format = attr.ib(default=None) label = attr.ib(default=None) @@ -1314,19 +1323,30 @@ class Graph(Panel): Generates Graph panel json structure. :param alert: List of AlertConditions + :param align: Select to align left and right Y-axes by value + :param alignLevel: Available when Align is selected. Value to use for alignment of left and right Y-axes + :param bars: Display values as a bar chart :param dataLinks: List of data links hooked to datapoints on the graph - :param dataSource: DataSource's name - :param minSpan: Minimum width for each panel + :param fill: Area fill, amount of color fill for a series. (default 1, 0 is none) + :param fillGradient: Degree of gradient on the area fill. (0 is no gradient, 10 is a steep gradient. Default is 0.) + :param lines: Display values as a line graph + :param points: Display points for values (default False) + :param pointRadius: Controls how large the points are + :param stack: Each series is stacked on top of another + :param percentage: Available when Stack is selected. Each series is drawn as a percentage of the total of all series :param thresholds: List of GraphThresholds - Only valid when alert not defined """ alert = attr.ib(default=None) alertThreshold = attr.ib(default=True, validator=instance_of(bool)) aliasColors = attr.ib(default=attr.Factory(dict)) + align = attr.ib(default=False, validator=instance_of(bool)) + alignLevel = attr.ib(default=0, validator=instance_of(int)) bars = attr.ib(default=False, validator=instance_of(bool)) dataLinks = attr.ib(default=attr.Factory(list)) error = attr.ib(default=False, validator=instance_of(bool)) fill = attr.ib(default=1, validator=instance_of(int)) + fillGradient = attr.ib(default=0, validator=instance_of(int)) grid = attr.ib(default=attr.Factory(Grid), validator=instance_of(Grid)) isNew = attr.ib(default=True, validator=instance_of(bool)) legend = attr.ib( @@ -1349,7 +1369,6 @@ class Graph(Panel): ) thresholds = attr.ib(default=attr.Factory(list)) xAxis = attr.ib(default=attr.Factory(XAxis), validator=instance_of(XAxis)) - # XXX: This isn't a *good* default, rather it's the default Grafana uses. try: yAxes = attr.ib( default=attr.Factory(YAxes), @@ -1392,6 +1411,10 @@ def to_json_data(self): 'type': GRAPH_TYPE, 'xaxis': self.xAxis, 'yaxes': self.yAxes, + 'yaxis': { + 'align': self.align, + 'alignLevel': self.alignLevel + } } if self.alert: graphObject['alert'] = self.alert @@ -2515,8 +2538,10 @@ class PieChart(Panel): :param aliasColors: dictionary of color overrides :param format: defines value units :param pieType: defines the shape of the pie chart (pie or donut) + :param percentageDecimals: Number of decimal places to show if percentages shown in legned :param showLegend: defines if the legend should be shown :param showLegendValues: defines if the legend should show values + :param showLegendPercentage: Show percentages in the legend :param legendType: defines where the legend position :param thresholds: defines thresholds """ @@ -2525,8 +2550,10 @@ class PieChart(Panel): format = attr.ib(default='none') legendType = attr.ib(default='Right side') pieType = attr.ib(default='pie') + percentageDecimals = attr.ib(default=0, validator=instance_of(int)) showLegend = attr.ib(default=True) showLegendValues = attr.ib(default=True) + showLegendPercentage = attr.ib(default=False, validator=instance_of(bool)) thresholds = attr.ib(default="") def to_json_data(self): @@ -2544,7 +2571,9 @@ def to_json_data(self): }, 'legend': { 'show': self.showLegend, - 'values': self.showLegendValues + 'values': self.showLegendValues, + 'percentage': self.showLegendPercentage, + 'percentageDecimals': self.percentageDecimals }, 'legendType': self.legendType, 'type': PIE_CHART_TYPE, From b6eaca695452ece1ae4d5d1c294d1bf54ad41e22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jul 2021 12:01:36 +0000 Subject: [PATCH 250/403] Bump sphinx from 4.0.2 to 4.1.1 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.0.2 to 4.1.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.0.2...v4.1.1) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 5e75e836..95f1a6a1 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.0.2 +sphinx == 4.1.1 sphinx_rtd_theme == 0.5.2 \ No newline at end of file From d5acc08673b4abc0f0dd4eae46863037de82563d Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 19 Jul 2021 14:19:45 +0200 Subject: [PATCH 251/403] Fix some sphinx errors I fixed by running 'python setup.py install --user' and 'make html -C docs' a couple of times. Signed-off-by: Daniel Holbach --- CHANGELOG.rst | 4 ++-- grafanalib/core.py | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ffd4bc02..7dd24b9e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -20,7 +20,7 @@ Changes * ... 0.5.13 (2021-05-17) -================== +=================== * Added a test for the Alert class. @@ -31,7 +31,7 @@ Changes * Moved the alertRuleTag field from Graph to Alert. 0.5.12 (2021-04-24) -================== +=================== * Added hide parameter to CloudwatchMetricsTarget class * Added table-driven example dashboard and upload script diff --git a/grafanalib/core.py b/grafanalib/core.py index f31092f0..136e6d85 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1562,8 +1562,8 @@ class AlertList(object): :param span: Defines the number of spans that will be used for the panel. :param stateFilter: Show alerts with statuses from the stateFilter list. The list can contain a subset of the following statuses: - [ALERTLIST_STATE_ALERTING, ALERTLIST_STATE_OK, ALERTLIST_STATE_NO_DATA, - ALERTLIST_STATE_PAUSED, ALERTLIST_STATE_EXECUTION_ERROR, ALERTLIST_STATE_PENDING]. + [ALERTLIST_STATE_ALERTING, ALERTLIST_STATE_OK, ALERTLIST_STATE_NO_DATA, + ALERTLIST_STATE_PAUSED, ALERTLIST_STATE_EXECUTION_ERROR, ALERTLIST_STATE_PENDING]. An empty list means all alerts. :param title: The panel title. :param transparent: If true, display the panel without a background. @@ -2678,8 +2678,8 @@ class Threshold(object): Example:: thresholds = [ Threshold('green', 0, 0.0), - Threshold('red', 1, 80.0) - ] + Threshold('red', 1, 80.0)] + """ color = attr.ib() @@ -2718,8 +2718,9 @@ class GraphThreshold(object): Example: thresholds = [ GraphThreshold(colorMode="ok", value=10.0), - GrpahThreshold(colorMode="critical", value=90.0) - ] + GraphThreshold(colorMode="critical", value=90.0) + ] + """ value = attr.ib(validator=instance_of(float)) From 45adc3d2416242554723fd28cd021158352cd267 Mon Sep 17 00:00:00 2001 From: James Gibson Date: Fri, 23 Jul 2021 15:58:40 +0100 Subject: [PATCH 252/403] Add documentation for elasticsearch orderBy --- grafanalib/elasticsearch.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 242dc48b..7bafe495 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -282,7 +282,8 @@ class TermsGroupBy(object): :param field: name of the field to group by :param minDocCount: min. amount of matching records to return a result :param order: ORDER_ASC or ORDER_DESC - :param orderBy: term to order the bucker + :param orderBy: term to order the bucket Term value: '_term', Doc Count: '_count' + or to use metric function use the string value "2" :param size: how many buckets are returned """ field = attr.ib(validator=instance_of(str)) From 594e44c3ace24dd5cd8bbccd05aa0faf92cfb8b0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Aug 2021 14:42:35 +0100 Subject: [PATCH 253/403] Bump sphinx from 4.1.1 to 4.1.2 (#388) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.1.1 to 4.1.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.1.1...v4.1.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 95f1a6a1..03401405 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.1.1 +sphinx == 4.1.2 sphinx_rtd_theme == 0.5.2 \ No newline at end of file From c475af024768b6123e36f4a82d40e2b658052ff0 Mon Sep 17 00:00:00 2001 From: Yegor Date: Mon, 23 Aug 2021 12:28:16 +0300 Subject: [PATCH 254/403] Fixed grammar error (#389) --- grafanalib/_gen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index 0175a2a6..45653512 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -135,5 +135,5 @@ def generate_dashboards_script(): def generate_dashboard_script(): - """Entry point for generate-dasboard.""" + """Entry point for generate-dashboard.""" run_script(generate_dashboard) From b207dab88ec225b80c311e5eeff2a33ed2cddaad Mon Sep 17 00:00:00 2001 From: Chris Fleming Date: Mon, 13 Sep 2021 14:17:04 +0100 Subject: [PATCH 255/403] Add inline script support for Elasticsearch metrics (#391) * Added support for inline scripts to metrics * Fix types * lint checks * Added Changelog entry --- CHANGELOG.rst | 1 + grafanalib/elasticsearch.py | 53 ++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7dd24b9e..50e00bbc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -13,6 +13,7 @@ x.x.x (TBD) * Added missing align to graph panel * Added missing show percentage attribute to Pie chart panel * Added ``extraJson`` attribute to the Panel class for overriding the panel with raw JSON +* Added inline script support for Elasticsearch metrics Changes ------- diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 7bafe495..c28d2c15 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -19,17 +19,25 @@ class CountMetricAgg(object): It's the default aggregator for elasticsearch queries. :param hide: show/hide the metric in the final panel display :param id: id of the metric + :param inline: script to apply to the data, using '_value' """ id = attr.ib(default=0, validator=instance_of(int)) hide = attr.ib(default=False, validator=instance_of(bool)) + inline = attr.ib(default="", validator=instance_of(str)) def to_json_data(self): + self.settings = {} + + if self.inline: + self.settings['script'] = {'inline': self.inline} + return { 'id': str(self.id), 'hide': self.hide, 'type': 'count', 'field': 'select field', - 'settings': {}, + 'inlineScript': self.inline, + 'settings': self.settings, } @@ -42,18 +50,26 @@ class MaxMetricAgg(object): :param field: name of elasticsearch field to provide the maximum for :param hide: show/hide the metric in the final panel display :param id: id of the metric + :param inline: script to apply to the data, using '_value' """ field = attr.ib(default="", validator=instance_of(str)) id = attr.ib(default=0, validator=instance_of(int)) hide = attr.ib(default=False, validator=instance_of(bool)) + inline = attr.ib(default="", validator=instance_of(str)) def to_json_data(self): + self.settings = {} + + if self.inline: + self.settings['script'] = {'inline': self.inline} + return { 'id': str(self.id), 'hide': self.hide, 'type': 'max', 'field': self.field, - 'settings': {}, + 'inlineScript': self.inline, + 'settings': self.settings, } @@ -66,18 +82,26 @@ class CardinalityMetricAgg(object): :param field: name of elasticsearch field to provide the maximum for :param id: id of the metric :param hide: show/hide the metric in the final panel display + :param inline: script to apply to the data, using '_value' """ field = attr.ib(default="", validator=instance_of(str)) id = attr.ib(default=0, validator=instance_of(int)) hide = attr.ib(default=False, validator=instance_of(bool)) + inline = attr.ib(default="", validator=instance_of(str)) def to_json_data(self): + self.settings = {} + + if self.inline: + self.settings['script'] = {'inline': self.inline} + return { 'id': str(self.id), 'hide': self.hide, 'type': 'cardinality', 'field': self.field, - 'settings': {}, + 'inlineScript': self.inline, + 'settings': self.settings, } @@ -90,19 +114,27 @@ class AverageMetricAgg(object): :param field: name of elasticsearch field to provide the maximum for :param id: id of the metric :param hide: show/hide the metric in the final panel display + :param inline: script to apply to the data, using '_value' """ field = attr.ib(default="", validator=instance_of(str)) id = attr.ib(default=0, validator=instance_of(int)) hide = attr.ib(default=False, validator=instance_of(bool)) + inline = attr.ib(default="", validator=instance_of(str)) def to_json_data(self): + self.settings = {} + + if self.inline: + self.settings['script'] = {'inline': self.inline} + return { 'id': str(self.id), 'hide': self.hide, 'type': 'avg', 'field': self.field, - 'settings': {}, + 'inlineScript': self.inline, + 'settings': self.settings, 'meta': {} } @@ -147,18 +179,26 @@ class SumMetricAgg(object): :param field: name of elasticsearch field to provide the sum over :param hide: show/hide the metric in the final panel display :param id: id of the metric + :param inline: script to apply to the data, using '_value' """ field = attr.ib(default="", validator=instance_of(str)) id = attr.ib(default=0, validator=instance_of(int)) hide = attr.ib(default=False, validator=instance_of(bool)) + inline = attr.ib(default="", validator=instance_of(str)) def to_json_data(self): + self.settings = {} + + if self.inline: + self.settings['script'] = {'inline': self.inline} + return { - 'type': 'sum', 'id': str(self.id), 'hide': self.hide, + 'type': 'sum', 'field': self.field, - 'settings': {}, + 'inlineScript': self.inline, + 'settings': self.settings, } @@ -221,6 +261,7 @@ def to_json_data(self): }) return { + 'field': 'select field', 'type': 'bucket_script', 'id': str(self.id), 'hide': self.hide, From 3f36d97b5d5b51fc0cfb84a190d6438837318077 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 14 Sep 2021 09:21:08 +0200 Subject: [PATCH 256/403] quote email to help link checker DTRT (#393) Signed-off-by: Daniel Holbach --- docs/CODE_OF_CONDUCT.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/CODE_OF_CONDUCT.rst b/docs/CODE_OF_CONDUCT.rst index ecb796f6..528dd2e3 100644 --- a/docs/CODE_OF_CONDUCT.rst +++ b/docs/CODE_OF_CONDUCT.rst @@ -6,7 +6,6 @@ Weaveworks follows the `CNCF Community Code of Conduct v1.0`_. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a Weaveworks project maintainer, -or -Alexis Richardson . +or `Alexis Richardson `. .. _`CNCF Community Code of Conduct v1.0`: https://github.com/cncf/foundation/blob/0ce4694e5103c0c24ca90c189da81e5408a46632/code-of-conduct.md From 76161d6b71f0c913d75b8d01efefdb3d34e16254 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 08:23:16 +0100 Subject: [PATCH 257/403] Bump sphinx from 4.1.2 to 4.2.0 (#392) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.1.2 to 4.2.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.1.2...v4.2.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 03401405..5900be2f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.1.2 +sphinx == 4.2.0 sphinx_rtd_theme == 0.5.2 \ No newline at end of file From 54d0fda172e638c8254dd757e39c05b699fde72f Mon Sep 17 00:00:00 2001 From: Chris Fleming Date: Tue, 14 Sep 2021 14:52:03 +0100 Subject: [PATCH 258/403] Templating (#390) * Selected needs to have a bool value for templating to work * Updated Changelog --- CHANGELOG.rst | 1 + grafanalib/core.py | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 50e00bbc..469a2843 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ x.x.x (TBD) * Added missing show percentage attribute to Pie chart panel * Added ``extraJson`` attribute to the Panel class for overriding the panel with raw JSON * Added inline script support for Elasticsearch metrics +* Seletcted needs to be set as a bool value for templating to work. Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index 136e6d85..36100a6a 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -777,6 +777,7 @@ def __attrs_post_init__(self): break else: self._current = { + 'selected': False if self.default is None or not self.default else True, 'text': self.default, 'value': self.default, 'tags': [], From 64dfc0bc80bc988d659217fb78ec0f66d5fdd8f0 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 14 Sep 2021 16:10:15 +0100 Subject: [PATCH 259/403] Bump version number in prep for release (#394) --- CHANGELOG.rst | 9 ++------- setup.py | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 469a2843..5beca46f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,7 @@ Changelog ========= -x.x.x (TBD) +0.5.14 (2021-09-14) ================== * Added colour overrides to pie chart panel @@ -14,12 +14,7 @@ x.x.x (TBD) * Added missing show percentage attribute to Pie chart panel * Added ``extraJson`` attribute to the Panel class for overriding the panel with raw JSON * Added inline script support for Elasticsearch metrics -* Seletcted needs to be set as a bool value for templating to work. - -Changes -------- - -* ... +* Selected needs to be set as a bool value for templating to work. 0.5.13 (2021-05-17) =================== diff --git a/setup.py b/setup.py index 749defea..18c841a6 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.13', + version='0.5.14', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From da60a25e135b24414699e7a45edcbbfb6fd4264d Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 14 Sep 2021 16:18:45 +0100 Subject: [PATCH 260/403] Add blank changelog for next release (#395) --- CHANGELOG.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5beca46f..ed265c00 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,16 @@ Changelog ========= +x.x.x (TBD) +=================== + +* Added ... + +Changes +------- + +* Changed ... + 0.5.14 (2021-09-14) ================== From b4ac7e6c3fbb16637290d5ec7419d862dccf745a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Sep 2021 14:28:11 +0100 Subject: [PATCH 261/403] Bump sphinx-rtd-theme from 0.5.2 to 1.0.0 (#396) Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 0.5.2 to 1.0.0. - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/0.5.2...1.0.0) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 5900be2f..b17688d5 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx == 4.2.0 -sphinx_rtd_theme == 0.5.2 \ No newline at end of file +sphinx_rtd_theme == 1.0.0 \ No newline at end of file From 850c02b7fec3f2c28ba5ceef8ead9172aa461f58 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 21 Sep 2021 12:01:33 +0200 Subject: [PATCH 262/403] update copyright (#397) Signed-off-by: Daniel Holbach --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 487d4846..f530f9c8 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,7 +19,7 @@ # -- Project information ----------------------------------------------------- project = 'grafanalib' -copyright = '2020, grafanalib community' +copyright = '2021, grafanalib community' author = 'grafanalib community' From e3592ea9bdd325ad5be4799df986d6e5cad11c9c Mon Sep 17 00:00:00 2001 From: milkpirate Date: Thu, 30 Sep 2021 10:18:09 +0200 Subject: [PATCH 263/403] Stat value mappings with color option (#404) Co-authored-by: Paul Schroeder Co-authored-by: James Gibson --- CHANGELOG.rst | 7 +- grafanalib/core.py | 116 ++++++++++++++++++++++++++++++++-- grafanalib/tests/test_core.py | 52 +++++++++++++++ 3 files changed, 169 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ed265c00..50511389 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,12 +5,15 @@ Changelog x.x.x (TBD) =================== -* Added ... +Added +----- + +* Support for colors in stat mapping panel with StatValueMappings & StatRangeMappings Changes ------- -* Changed ... +* Deprecated StatMapping, StatValueMapping & StatRangeMapping 0.5.14 (2021-09-14) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 36100a6a..38585c53 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1,3 +1,4 @@ + """Low-level functions for building Grafana dashboards. The functions in this module don't enforce Weaveworks policy, and only mildly @@ -1680,9 +1681,108 @@ def to_json_data(self): ) +@attr.s +class StatValueMappingItem(object): + """ + Generates json structure for the value mapping item for the StatValueMappings class: + + :param text: String that will replace input value + :param mapValue: Value to be replaced + :param color: How to color the text if mapping occurs + :param index: index + """ + + text = attr.ib() + mapValue = attr.ib(default="", validator=instance_of(str)) + color = attr.ib(default="", validator=instance_of(str)) + index = attr.ib(default=None) + + def to_json_data(self): + return { + self.mapValue: { + 'text': self.text, + 'color': self.color, + 'index': self.index + } + } + + +@attr.s(init=False) +class StatValueMappings(object): + """ + Generates json structure for the value mappings for the StatPanel: + + :param mappingItems: List of StatValueMappingItem objects + + mappings=[ + core.StatValueMappings( + core.StatValueMappingItem('Offline', '0', 'red'), # Value must a string + core.StatValueMappingItem('Online', '1', 'green') + ), + ], + """ + + mappingItems = attr.ib( + default=[], + validator=attr.validators.deep_iterable( + member_validator=attr.validators.instance_of(StatValueMappingItem), + iterable_validator=attr.validators.instance_of(list), + ), + ) + + def __init__(self, *mappings: StatValueMappingItem): + self.__attrs_init__([*mappings]) + + def to_json_data(self): + ret_dict = { + 'type': 'value', + 'options': { + } + } + + for item in self.mappingItems: + ret_dict['options'].update(item.to_json_data()) + + return ret_dict + + +@attr.s +class StatRangeMappings(object): + """ + Generates json structure for the range mappings for the StatPanel: + + :param text: Sting that will replace input value + :param startValue: When using a range, the start value of the range + :param endValue: When using a range, the end value of the range + :param color: How to color the text if mapping occurs + :param index: index + """ + + text = attr.ib() + startValue = attr.ib(default=0, validator=instance_of(int)) + endValue = attr.ib(default=0, validator=instance_of(int)) + color = attr.ib(default="", validator=instance_of(str)) + index = attr.ib(default=None) + + def to_json_data(self): + return { + 'type': 'range', + 'options': { + 'from': self.startValue, + 'to': self.endValue, + 'result': { + 'text': self.text, + 'color': self.color, + 'index': self.index + } + } + } + + @attr.s class StatMapping(object): """ + Deprecated Grafana v8 Generates json structure for the value mapping for the Stat panel: :param text: Sting that will replace input value @@ -1701,7 +1801,7 @@ class StatMapping(object): def to_json_data(self): mappingType = MAPPING_TYPE_VALUE_TO_TEXT if self.mapValue else MAPPING_TYPE_RANGE_TO_TEXT - return { + ret_dict = { 'operator': '', 'text': self.text, 'type': mappingType, @@ -1711,14 +1811,17 @@ def to_json_data(self): 'id': self.id } + return ret_dict + @attr.s class StatValueMapping(object): """ + Deprecated Grafana v8 Generates json structure for the value mappings for the StatPanel: :param text: Sting that will replace input value - :param value: Value to be replaced + :param mapValue: Value to be replaced :param id: panel id """ @@ -1727,13 +1830,18 @@ class StatValueMapping(object): id = attr.ib(default=None) def to_json_data(self): - return StatMapping(self.text, mapValue=self.mapValue, id=self.id) + return StatMapping( + self.text, + mapValue=self.mapValue, + id=self.id, + ) @attr.s class StatRangeMapping(object): """ - Generates json structure for the value mappings for the StatPanel: + Deprecated Grafana v8 + Generates json structure for the range mappings for the StatPanel: :param text: Sting that will replace input value :param startValue: When using a range, the start value of the range diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index a0f1f855..ca7b2a92 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -2,6 +2,7 @@ import random import grafanalib.core as G +import pytest def dummy_grid_pos() -> G.GridPos: @@ -158,6 +159,57 @@ def test_stat_no_repeat(): assert t.to_json_data()['maxPerRow'] is None +def test_StatValueMappings_exception_checks(): + with pytest.raises(TypeError): + G.StatValueMappings( + G.StatValueMappingItem('foo', '0', 'dark-red'), + "not of type StatValueMappingItem", + ) + + +def test_StatValueMappings(): + t = G.StatValueMappings( + G.StatValueMappingItem('foo', '0', 'dark-red'), # Value must a string + G.StatValueMappingItem('bar', '1', 'purple'), + ) + + json_data = t.to_json_data() + assert json_data['type'] == 'value' + assert json_data['options']['0']['text'] == 'foo' + assert json_data['options']['0']['color'] == 'dark-red' + assert json_data['options']['1']['text'] == 'bar' + assert json_data['options']['1']['color'] == 'purple' + + +def test_StatRangeMappings(): + t = G.StatRangeMappings( + 'dummy_text', + startValue=10, + endValue=20, + color='dark-red' + ) + + json_data = t.to_json_data() + assert json_data['type'] == 'range' + assert json_data['options']['from'] == 10 + assert json_data['options']['to'] == 20 + assert json_data['options']['result']['text'] == 'dummy_text' + assert json_data['options']['result']['color'] == 'dark-red' + + +def test_StatMapping(): + t = G.StatMapping( + 'dummy_text', + startValue='foo', + endValue='bar', + ) + + json_data = t.to_json_data() + assert json_data['text'] == 'dummy_text' + assert json_data['from'] == 'foo' + assert json_data['to'] == 'bar' + + def test_stat_with_repeat(): t = G.Stat( title='dummy', From 3d55775b0fa64527fa4e2332468d7edf855a4f41 Mon Sep 17 00:00:00 2001 From: milkpirate Date: Thu, 30 Sep 2021 10:23:35 +0200 Subject: [PATCH 264/403] Discrete panel (#403) Co-authored-by: Paul Schroeder Co-authored-by: JamesGibo --- CHANGELOG.rst | 7 +++--- grafanalib/core.py | 45 +++++++++++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 36 ++++++++++++++++++++++++++++ grafanalib/validators.py | 2 +- 4 files changed, 85 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 50511389..b74e705e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,14 +5,13 @@ Changelog x.x.x (TBD) =================== -Added ------ - -* Support for colors in stat mapping panel with StatValueMappings & StatRangeMappings +* Added Discrete panel +* Added support for colors in stat mapping panel with StatValueMappings & StatRangeMappings Changes ------- +* Refine expectations of is_color_code * Deprecated StatMapping, StatValueMapping & StatRangeMapping 0.5.14 (2021-09-14) diff --git a/grafanalib/core.py b/grafanalib/core.py index 38585c53..fbb2b772 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -77,6 +77,7 @@ def to_json_data(self): DASHBOARD_TYPE = 'dashboard' ROW_TYPE = 'row' GRAPH_TYPE = 'graph' +DISCRETE_TYPE = 'natel-discrete-panel' STAT_TYPE = 'stat' SINGLESTAT_TYPE = 'singlestat' TABLE_TYPE = 'table' @@ -1453,9 +1454,53 @@ def auto_ref_ids(self): def set_refid(t): return t if t.refId else attr.evolve(t, refId=next(auto_ref_ids)) + return self._map_targets(set_refid) +@attr.s +class DiscreteColorMappingItem(object): + """ + Generates json structure for the value mapping item for the StatValueMappings class: + + :param text: String to color + :param color: To color the text with + """ + + text = attr.ib(validator=instance_of(str)) + color = attr.ib(default=GREY1, validator=instance_of((str, RGBA))) + + def to_json_data(self): + return { + "color": self.color, + "text": self.text, + } + + +@attr.s +class Discrete(Panel): + """ + Generates Discrete panel json structure. + + :param colorMaps: List of DiscreteColorMappingItem, to color values. + """ + + colorMapsItems = attr.ib( + default=[], + validator=attr.validators.deep_iterable( + member_validator=attr.validators.instance_of(DiscreteColorMappingItem), + iterable_validator=attr.validators.instance_of(list), + ), + ) + + def to_json_data(self): + graphObject = { + 'colorMaps': self.colorMapsItems, + 'type': DISCRETE_TYPE, + } + return self.panel_json(graphObject) + + @attr.s class SparkLine(object): fillColor = attr.ib( diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index ca7b2a92..d79ddb6e 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -159,6 +159,42 @@ def test_stat_no_repeat(): assert t.to_json_data()['maxPerRow'] is None +def test_DiscreteColorMappingItem_exception_checks(): + with pytest.raises(TypeError): + G.DiscreteColorMappingItem(123) + + with pytest.raises(TypeError): + G.DiscreteColorMappingItem("foo", color=123) + + +def test_DiscreteColorMappingItem(): + t = G.DiscreteColorMappingItem('foo') + + json_data = t.to_json_data() + assert json_data['text'] == 'foo' + assert json_data['color'] == G.GREY1 + + t = G.DiscreteColorMappingItem('foo', color='bar') + + json_data = t.to_json_data() + assert json_data['text'] == 'foo' + assert json_data['color'] == 'bar' + + +def test_Discrete(): + colorMap = [ + G.DiscreteColorMappingItem('bar', color='baz'), + G.DiscreteColorMappingItem('foz', color='faz') + ] + + t = G.Discrete('foo', colorMapsItems=colorMap) + + json_data = t.to_json_data() + assert json_data['colorMaps'] == colorMap + assert json_data['title'] == '' + assert json_data['type'] == G.DISCRETE_TYPE + + def test_StatValueMappings_exception_checks(): with pytest.raises(TypeError): G.StatValueMappings( diff --git a/grafanalib/validators.py b/grafanalib/validators.py index 16584f35..e7c69a2c 100644 --- a/grafanalib/validators.py +++ b/grafanalib/validators.py @@ -47,7 +47,7 @@ def is_color_code(instance, attribute, value): Value considered as valid color code if it starts with # char followed by hexadecimal. """ - err = "{attr} should be a valid color code".format(attr=attribute.name) + err = "{attr} should be a valid color code (e.g. #37872D)".format(attr=attribute.name) if not value.startswith("#"): raise ValueError(err) if len(value) != 7: From 0a37a80b8cee733e163989209d1921fe6cf4d98a Mon Sep 17 00:00:00 2001 From: locan11 <83332989+locan11@users.noreply.github.com> Date: Thu, 30 Sep 2021 10:31:40 +0200 Subject: [PATCH 265/403] Do not default to 0 for min value for yAxes (#399) --- grafanalib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index fbb2b772..df89ed00 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -489,7 +489,7 @@ class YAxis(object): label = attr.ib(default=None) logBase = attr.ib(default=1) max = attr.ib(default=None) - min = attr.ib(default=0) + min = attr.ib(default=None) show = attr.ib(default=True, validator=instance_of(bool)) def to_json_data(self): From ad4500f047aa54a73f107d6f998d1ac73d5a924a Mon Sep 17 00:00:00 2001 From: locan11 <83332989+locan11@users.noreply.github.com> Date: Thu, 30 Sep 2021 11:50:11 +0200 Subject: [PATCH 266/403] Added missing auto interval properties in Template (#398) * Added missing auto interval properties in Template * Do not default to 0 for min value for yAxes --- CHANGELOG.rst | 2 +- grafanalib/core.py | 19 ++++++++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b74e705e..dd40d167 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,7 +16,7 @@ Changes 0.5.14 (2021-09-14) ================== - +* Added missing auto interval properties in Template * Added colour overrides to pie chart panel * Added missing attributes from xAxis class * Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/transformations/types-options/#transformation-types-and-options) diff --git a/grafanalib/core.py b/grafanalib/core.py index df89ed00..ba65901f 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -257,6 +257,8 @@ def to_json_data(self): GAUGE_DISPLAY_MODE_LCD = 'lcd' GAUGE_DISPLAY_MODE_GRADIENT = 'gradient' +DEFAULT_AUTO_COUNT = 30 +DEFAULT_MIN_AUTO_INTERVAL = '10s' @attr.s class Mapping(object): @@ -728,6 +730,9 @@ class Template(object): interval, datasource, custom, constant, adhoc. :param hide: Hide this variable in the dashboard, can be one of: SHOW (default), HIDE_LABEL, HIDE_VARIABLE + :param auto: Interval will be dynamically calculated by dividing time range by the count specified in auto_count. + :param autoCount: Number of intervals for dividing the time range. + :param autoMin: Smallest interval for auto interval generator. """ name = attr.ib() @@ -758,7 +763,16 @@ class Template(object): type = attr.ib(default='query') hide = attr.ib(default=SHOW) sort = attr.ib(default=SORT_ALPHA_ASC) - + auto = attr.ib( + default=False, + validator=instance_of(bool), + ) + autoCount = attr.ib( + default=DEFAULT_AUTO_COUNT, + validator=instance_of(int) + ) + autoMin = attr.ib(default=DEFAULT_MIN_AUTO_INTERVAL) + def __attrs_post_init__(self): if self.type == 'custom': if len(self.options) == 0: @@ -804,6 +818,9 @@ def to_json_data(self): 'useTags': self.useTags, 'tagsQuery': self.tagsQuery, 'tagValuesQuery': self.tagValuesQuery, + 'auto': self.auto, + 'auto_min': self.autoMin, + 'auto_count': self.autoCount } From 3a469f1eeaf8abd09748512a26f133152e1d880c Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Thu, 30 Sep 2021 10:55:23 +0100 Subject: [PATCH 267/403] Tidy changelog and linting errors (#405) --- CHANGELOG.rst | 4 +++- grafanalib/core.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dd40d167..e3b0aac5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,16 +7,18 @@ x.x.x (TBD) * Added Discrete panel * Added support for colors in stat mapping panel with StatValueMappings & StatRangeMappings +* Added missing auto interval properties in Template Changes ------- * Refine expectations of is_color_code * Deprecated StatMapping, StatValueMapping & StatRangeMapping +* Change YAxis min value default from None to 0 0.5.14 (2021-09-14) ================== -* Added missing auto interval properties in Template + * Added colour overrides to pie chart panel * Added missing attributes from xAxis class * Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/transformations/types-options/#transformation-types-and-options) diff --git a/grafanalib/core.py b/grafanalib/core.py index ba65901f..af94c57f 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -260,6 +260,7 @@ def to_json_data(self): DEFAULT_AUTO_COUNT = 30 DEFAULT_MIN_AUTO_INTERVAL = '10s' + @attr.s class Mapping(object): @@ -772,7 +773,7 @@ class Template(object): validator=instance_of(int) ) autoMin = attr.ib(default=DEFAULT_MIN_AUTO_INTERVAL) - + def __attrs_post_init__(self): if self.type == 'custom': if len(self.options) == 0: From c27394cbef7ef5df32b9df3e4470ed62f8875e4c Mon Sep 17 00:00:00 2001 From: milkpirate Date: Mon, 4 Oct 2021 11:00:40 +0200 Subject: [PATCH 268/403] Discrete options (#407) * ignore venv and .idea * restructure and add supporting classes * update discrete class * add tests * correct chglg https://github.com/weaveworks/grafanalib/pull/399/files#diff-e183577edc3e65361ecd131b1d852e4b241b30f61edd379a745317aa49502339R490 * correct double def of ValueMap * add panel URL Co-authored-by: JamesGibo * correct indentation * add note to docs * add textSize * add textSizeTime Co-authored-by: Paul Schroeder Co-authored-by: JamesGibo --- .gitignore | 2 + CHANGELOG.rst | 2 +- grafanalib/core.py | 239 ++++++++++++++++++++++++++-------- grafanalib/tests/test_core.py | 60 ++++++++- 4 files changed, 249 insertions(+), 54 deletions(-) diff --git a/.gitignore b/.gitignore index 0baf52c0..4edeec2b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ test-results/junit-*.xml .ensure-* /.tox /.coverage +/venv/ +/.idea/ # Documentation docs/build diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e3b0aac5..7fb532f7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,7 +14,7 @@ Changes * Refine expectations of is_color_code * Deprecated StatMapping, StatValueMapping & StatRangeMapping -* Change YAxis min value default from None to 0 +* Change YAxis min value default from 0 to None 0.5.14 (2021-09-14) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index af94c57f..1d1cf55b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1477,46 +1477,24 @@ def set_refid(t): @attr.s -class DiscreteColorMappingItem(object): +class ValueMap(object): """ - Generates json structure for the value mapping item for the StatValueMappings class: + Generates json structure for a value mapping item. - :param text: String to color - :param color: To color the text with + :param op: comparison operator + :param value: value to map to text + :param text: text to map the value to """ - - text = attr.ib(validator=instance_of(str)) - color = attr.ib(default=GREY1, validator=instance_of((str, RGBA))) + text = attr.ib() + value = attr.ib() + op = attr.ib(default='=') def to_json_data(self): return { - "color": self.color, - "text": self.text, - } - - -@attr.s -class Discrete(Panel): - """ - Generates Discrete panel json structure. - - :param colorMaps: List of DiscreteColorMappingItem, to color values. - """ - - colorMapsItems = attr.ib( - default=[], - validator=attr.validators.deep_iterable( - member_validator=attr.validators.instance_of(DiscreteColorMappingItem), - iterable_validator=attr.validators.instance_of(list), - ), - ) - - def to_json_data(self): - graphObject = { - 'colorMaps': self.colorMapsItems, - 'type': DISCRETE_TYPE, + 'op': self.op, + 'text': self.text, + 'value': self.value, } - return self.panel_json(graphObject) @attr.s @@ -1542,16 +1520,21 @@ def to_json_data(self): @attr.s -class ValueMap(object): - op = attr.ib() - text = attr.ib() - value = attr.ib() +class Gauge(object): + + minValue = attr.ib(default=0, validator=instance_of(int)) + maxValue = attr.ib(default=100, validator=instance_of(int)) + show = attr.ib(default=False, validator=instance_of(bool)) + thresholdLabels = attr.ib(default=False, validator=instance_of(bool)) + thresholdMarkers = attr.ib(default=True, validator=instance_of(bool)) def to_json_data(self): return { - 'op': self.op, - 'text': self.text, - 'value': self.value, + 'maxValue': self.maxValue, + 'minValue': self.minValue, + 'show': self.show, + 'thresholdLabels': self.thresholdLabels, + 'thresholdMarkers': self.thresholdMarkers, } @@ -1570,22 +1553,176 @@ def to_json_data(self): @attr.s -class Gauge(object): +class DiscreteColorMappingItem(object): + """ + Generates json structure for the value mapping item for the StatValueMappings class: - minValue = attr.ib(default=0, validator=instance_of(int)) - maxValue = attr.ib(default=100, validator=instance_of(int)) - show = attr.ib(default=False, validator=instance_of(bool)) - thresholdLabels = attr.ib(default=False, validator=instance_of(bool)) - thresholdMarkers = attr.ib(default=True, validator=instance_of(bool)) + :param text: String to color + :param color: To color the text with + """ + + text = attr.ib(validator=instance_of(str)) + color = attr.ib(default=GREY1, validator=instance_of((str, RGBA))) def to_json_data(self): return { - 'maxValue': self.maxValue, - 'minValue': self.minValue, - 'show': self.show, - 'thresholdLabels': self.thresholdLabels, - 'thresholdMarkers': self.thresholdMarkers, + "color": self.color, + "text": self.text, + } + + +@attr.s +class Discrete(Panel): + """ + Generates Discrete panel json structure. + https://grafana.com/grafana/plugins/natel-discrete-panel/ + + :param colorMaps: list of DiscreteColorMappingItem, to color values + (note these apply **after** value mappings) + :param backgroundColor: dito + :param lineColor: Separator line color between rows + :param metricNameColor: dito + :param timeTextColor: dito + :param valueTextColor: dito + + :param decimals: number of decimals to display + :param rowHeight: dito + + :param units: defines value units + :param legendSortBy: time (desc: '-ms', asc: 'ms), count (desc: '-count', asc: 'count') + + :param highlightOnMouseover: whether to highlight the state of hovered time falls in. + :param showLegend: dito + :param showLegendPercent: whether to show percentage of time spent in each state/value + :param showLegendNames: + :param showLegendValues: whether to values in legend + :param legendPercentDecimals: number of decimals for legend + :param showTimeAxis: dito + :param use12HourClock: dito + :param writeMetricNames: dito + :param writeLastValue: dito + :param writeAllValues: whether to show all values + + :param showDistinctCount: whether to show distinct values count + :param showLegendCounts: whether to show value occurrence count + :param showLegendTime: whether to show of each state + :param showTransitionCount: whether to show transition count + + :param colorMaps: list of DiscreteColorMappingItem + :param rangeMaps: list of RangeMap + :param valueMaps: list of ValueMap + """ + + backgroundColor = attr.ib( + default=RGBA(128, 128, 128, 0.1), + validator=instance_of((RGBA, RGB, str)) + ) + lineColor = attr.ib( + default=RGBA(0, 0, 0, 0.1), + validator=instance_of((RGBA, RGB, str)) + ) + metricNameColor = attr.ib( + default="#000000", + validator=instance_of((RGBA, RGB, str)) + ) + timeTextColor = attr.ib( + default="#d8d9da", + validator=instance_of((RGBA, RGB, str)) + ) + valueTextColor = attr.ib( + default="#000000", + validator=instance_of((RGBA, RGB, str)) + ) + + decimals = attr.ib(default=0, validator=instance_of(int)) + legendPercentDecimals = attr.ib(default=0, validator=instance_of(int)) + rowHeight = attr.ib(default=50, validator=instance_of(int)) + textSize = attr.ib(default=24, validator=instance_of(int)) + + textSizeTime = attr.ib(default=12, validator=instance_of(int)) + units = attr.ib(default="none", validator=instance_of(str)) + legendSortBy = attr.ib( + default="-ms", + validator=in_(['-ms', 'ms', '-count', 'count']) + ) + + highlightOnMouseover = attr.ib(default=True, validator=instance_of(bool)) + showLegend = attr.ib(default=True, validator=instance_of(bool)) + showLegendPercent = attr.ib(default=True, validator=instance_of(bool)) + showLegendNames = attr.ib(default=True, validator=instance_of(bool)) + showLegendValues = attr.ib(default=True, validator=instance_of(bool)) + showTimeAxis = attr.ib(default=True, validator=instance_of(bool)) + use12HourClock = attr.ib(default=False, validator=instance_of(bool)) + writeMetricNames = attr.ib(default=False, validator=instance_of(bool)) + writeLastValue = attr.ib(default=True, validator=instance_of(bool)) + writeAllValues = attr.ib(default=False, validator=instance_of(bool)) + + showDistinctCount = attr.ib(default=None) + showLegendCounts = attr.ib(default=None) + showLegendTime = attr.ib(default=None) + showTransitionCount = attr.ib(default=None) + + colorMaps = attr.ib( + default=[], + validator=attr.validators.deep_iterable( + member_validator=instance_of(DiscreteColorMappingItem), + iterable_validator=instance_of(list), + ), + ) + rangeMaps = attr.ib( + default=[], + validator=attr.validators.deep_iterable( + member_validator=instance_of(RangeMap), + iterable_validator=instance_of(list), + ), + ) + valueMaps = attr.ib( + default=[], + validator=attr.validators.deep_iterable( + member_validator=instance_of(ValueMap), + iterable_validator=instance_of(list), + ), + ) + + def to_json_data(self): + graphObject = { + 'type': DISCRETE_TYPE, + + 'backgroundColor': self.backgroundColor, + 'lineColor': self.lineColor, + 'metricNameColor': self.metricNameColor, + 'timeTextColor': self.timeTextColor, + 'valueTextColor': self.valueTextColor, + 'legendPercentDecimals': self.legendPercentDecimals, + 'decimals': self.decimals, + 'rowHeight': self.rowHeight, + 'textSize': self.textSize, + 'textSizeTime': self.textSizeTime, + + 'units': self.units, + 'legendSortBy': self.legendSortBy, + + 'highlightOnMouseover': self.highlightOnMouseover, + 'showLegend': self.showLegend, + 'showLegendPercent': self.showLegendPercent, + 'showLegendNames': self.showLegendNames, + 'showLegendValues': self.showLegendValues, + 'showTimeAxis': self.showTimeAxis, + 'use12HourClock': self.use12HourClock, + 'writeMetricNames': self.writeMetricNames, + 'writeLastValue': self.writeLastValue, + 'writeAllValues': self.writeAllValues, + + 'showDistinctCount': self.showDistinctCount, + 'showLegendCounts': self.showLegendCounts, + 'showLegendTime': self.showLegendTime, + 'showTransitionCount': self.showTransitionCount, + + 'colorMaps': self.colorMaps, + 'rangeMaps': self.rangeMaps, + 'valueMaps': self.valueMaps, } + return self.panel_json(graphObject) @attr.s diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index d79ddb6e..54f2637d 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -181,18 +181,74 @@ def test_DiscreteColorMappingItem(): assert json_data['color'] == 'bar' +def test_Discrete_exceptions(): + with pytest.raises(ValueError): + G.Discrete(legendSortBy='foo') + + with pytest.raises(TypeError): + G.Discrete(rangeMaps=[123, 456]) + + with pytest.raises(TypeError): + G.Discrete(valueMaps=['foo', 'bar']) + + with pytest.raises(TypeError): + G.Discrete(lineColor=123) + + with pytest.raises(TypeError): + G.Discrete(highlightOnMouseover=123) + + def test_Discrete(): colorMap = [ G.DiscreteColorMappingItem('bar', color='baz'), G.DiscreteColorMappingItem('foz', color='faz') ] - t = G.Discrete('foo', colorMapsItems=colorMap) + t = G.Discrete( + title='foo', + colorMaps=colorMap, + lineColor='#aabbcc', + metricNameColor=G.RGBA(1, 2, 3, .5), + decimals=123, + highlightOnMouseover=False, + showDistinctCount=True, + showLegendCounts=False, + ) json_data = t.to_json_data() assert json_data['colorMaps'] == colorMap - assert json_data['title'] == '' + assert json_data['title'] == 'foo' assert json_data['type'] == G.DISCRETE_TYPE + assert json_data['rangeMaps'] == [] + assert json_data['valueMaps'] == [] + + assert json_data['backgroundColor'] == G.RGBA(128, 128, 128, 0.1) + assert json_data['lineColor'] == '#aabbcc' + assert json_data['metricNameColor'] == G.RGBA(1, 2, 3, .5) + assert json_data['timeTextColor'] == "#d8d9da" + assert json_data['valueTextColor'] == "#000000" + + assert json_data['decimals'] == 123 + assert json_data['legendPercentDecimals'] == 0 + assert json_data['rowHeight'] == 50 + assert json_data['textSize'] == 24 + assert json_data['textSizeTime'] == 12 + + assert json_data['highlightOnMouseover'] is False + assert json_data['showLegend'] is True + assert json_data['showLegendPercent'] is True + assert json_data['showLegendNames'] is True + assert json_data['showLegendValues'] is True + assert json_data['showTimeAxis'] is True + assert json_data['use12HourClock'] is False + assert json_data['writeMetricNames'] is False + assert json_data['writeLastValue'] is True + assert json_data['writeAllValues'] is False + + assert json_data['showDistinctCount'] is True + assert json_data['showLegendCounts'] is False + assert json_data['showLegendTime'] is None + assert json_data['showTransitionCount'] is None def test_StatValueMappings_exception_checks(): From e2bc07214a718917b0ae2255824ad316f460fe15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Oct 2021 12:02:12 +0000 Subject: [PATCH 269/403] Bump lycheeverse/lychee-action from 1.0.8 to 1.0.9 Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.0.8 to 1.0.9. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.0.8...v1.0.9) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index b8d6133b..31c91b66 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.0.8 + uses: lycheeverse/lychee-action@v1.0.9 with: args: --verbose **/*.html - name: Fail if there were link errors From 96ba5a075707ff34d634030b7547e6c098c6902d Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 18 Oct 2021 09:10:27 +0200 Subject: [PATCH 270/403] Move Matt to maintainer alumni (#411) Matt Richter stepped down as maintainer a couple of months back. Here I'm catching up the MAINTAINERS file to reflect this. Thanks for all your help and we hope to see you still around! All the best! Signed-off-by: Daniel Holbach --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 065b97f2..9759553b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7,11 +7,11 @@ https://weave-community.slack.com/ in #grafanalib (https://weave-community.slack Daniel Holbach, Weaveworks (github: @dholbach, slack: dholbach) James Gibson, BBC (github: @JamesGibo, slack: James G) -Matt Richter, Validity HQ (github: @matthewmrichter, slack: Matt Richter) Retired maintainers: - Bryan Boreham - Jonathan Lange +- Matt Richter Thank you for your involvement, and let us not say "farewell" ... From 667d70c8c45914f6c7817fe37dd3e68b8e7361b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gonz=C3=A1lez?= Date: Mon, 18 Oct 2021 09:21:07 +0200 Subject: [PATCH 271/403] Add support for Time Series panel (#350) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add timeseries panel Signed-off-by: Daniel González Lopes * Make flake8 happy and remove maxDataPoints Signed-off-by: Daniel González Lopes * add note to changelog * Add basic fields to time series panel * Add missing functionality Co-authored-by: James Gibson --- CHANGELOG.rst | 1 + grafanalib/core.py | 114 ++++++++++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 11 ++++ 3 files changed, 126 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7fb532f7..81c3bd42 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,7 @@ x.x.x (TBD) * Added Discrete panel * Added support for colors in stat mapping panel with StatValueMappings & StatRangeMappings * Added missing auto interval properties in Template +* Added support for time series panel added in Grafana v8 Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index 1d1cf55b..6564c930 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -91,6 +91,7 @@ def to_json_data(self): STATUSMAP_TYPE = 'flant-statusmap-panel' SVG_TYPE = 'marcuscalidus-svg-panel' PIE_CHART_TYPE = 'grafana-piechart-panel' +TIMESERIES_TYPE = 'timeseries' WORLD_MAP_TYPE = 'grafana-worldmap-panel' DEFAULT_FILL = 1 @@ -1476,6 +1477,119 @@ def set_refid(t): return self._map_targets(set_refid) +@attr.s +class TimeSeries(Panel): + """Generates Time Series panel json structure added in Grafana v8 + + Grafana doc on time series: https://grafana.com/docs/grafana/latest/panels/visualizations/time-series/ + + :param axisPlacement: auto(Default), left. right, hidden + :param axisLabel: axis label string + :param barAlignment: bar alignment + -1 (left), 0 (centre, default), 1 + :param colorMode: Color mode + palette-classic (Default), + :param drawStyle: how to display your time series data + line (Default), bars, points + :param fillOpacity: fillOpacity + :param gradientMode: gradientMode + :param legendDisplayMode: refine how the legend appears in your visualization + list (Default), table, hidden + :param legendPlacement: bottom (Default), right + :param lineInterpolation: line interpolation + linear (Default), smooth, stepBefore, stepAfter + :param lineWidth: line width, default 1 + :param mappings: To assign colors to boolean or string values, use Value mappings + :param pointSize: point size, default 5 + :param scaleDistributionType: axis scale linear or log + :param scaleDistributionLog: Base of if logarithmic scale type set, default 2 + :param spanNulls: connect null values, default False + :param showPoints: show points + auto (Default), always, never + :param stacking: dict to enable stacking, {"mode": "normal", "group": "A"} + :param thresholds: single stat thresholds + :param tooltipMode: When you hover your cursor over the visualization, Grafana can display tooltips + single (Default), multi, none + :param unit: units + """ + + axisPlacement = attr.ib(default='auto', validator=instance_of(str)) + axisLabel = attr.ib(default='', validator=instance_of(str)) + barAlignment = attr.ib(default=0, validator=instance_of(int)) + colorMode = attr.ib(default='palette-classic', validator=instance_of(str)) + drawStyle = attr.ib(default='line', validator=instance_of(str)) + fillOpacity = attr.ib(default=0, validator=instance_of(int)) + gradientMode = attr.ib(default='none', validator=instance_of(str)) + legendDisplayMode = attr.ib(default='list', validator=instance_of(str)) + legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) + lineInterpolation = attr.ib(default='linear', validator=instance_of(str)) + lineWidth = attr.ib(default=1, validator=instance_of(int)) + mappings = attr.ib(default=attr.Factory(list)) + pointSize = attr.ib(default=5, validator=instance_of(int)) + scaleDistributionType = attr.ib(default='linear', validator=instance_of(str)) + scaleDistributionLog = attr.ib(default=2, validator=instance_of(int)) + spanNulls = attr.ib(default=False, validator=instance_of(bool)) + showPoints = attr.ib(default='auto', validator=instance_of(str)) + stacking = attr.ib(default={}, validator=instance_of(dict)) + thresholds = attr.ib(default=attr.Factory(list)) + tooltipMode = attr.ib(default='single', validator=instance_of(str)) + unit = attr.ib(default='', validator=instance_of(str)) + + def to_json_data(self): + return self.panel_json( + { + 'fieldConfig': { + 'defaults': { + 'color': { + 'mode': self.colorMode + }, + 'custom': { + 'axisPlacement': self.axisPlacement, + 'axisLabel': self.axisLabel, + 'drawStyle': self.drawStyle, + 'lineInterpolation': self.lineInterpolation, + 'barAlignment': self.barAlignment, + 'lineWidth': self.lineWidth, + 'fillOpacity': self.fillOpacity, + 'gradientMode': self.gradientMode, + 'spanNulls': self.spanNulls, + 'showPoints': self.showPoints, + 'pointSize': self.pointSize, + 'stacking': self.stacking, + 'scaleDistribution': { + 'type': self.scaleDistributionType, + 'log': self.scaleDistributionLog + }, + 'hideFrom': { + 'tooltip': False, + 'viz': False, + 'legend': False + }, + }, + 'mappings': self.mappings, + 'thresholds': { + 'mode': 'absolute', + 'steps': self.thresholds + }, + 'unit': self.unit + }, + 'overrides': [] + }, + 'options': { + 'legend': { + 'displayMode': self.legendDisplayMode, + 'placement': self.legendPlacement, + 'calcs': [] + }, + 'tooltip': { + 'mode': self.tooltipMode + } + }, + 'type': TIMESERIES_TYPE, + } + ) + + @attr.s class ValueMap(object): """ diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 54f2637d..18cacd1f 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -500,3 +500,14 @@ def test_worldmap(): assert data['datasource'] == data_source assert data['title'] == title assert data['circleMaxSize'] == 11 + + +def test_timeseries(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + timeseries = G.TimeSeries(data_source, targets, title) + data = timeseries.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title From b737b1e24dbd05e642cb9160c4d0c655edd4b3c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Oct 2021 12:02:29 +0000 Subject: [PATCH 272/403] Bump actions/checkout from 2.3.4 to 2.3.5 Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.4 to 2.3.5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.3.4...v2.3.5) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 31c91b66..958795d0 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -7,7 +7,7 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - name: Set up Python uses: actions/setup-python@v2.2.2 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 3cd5db36..8ff25e0a 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -7,7 +7,7 @@ jobs: name: Build and publish Python 🐍 distributions 📦 to PyPI runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - name: Set up Python 3.7 uses: actions/setup-python@v2.2.2 with: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 51fe89ee..8cb22cda 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: matrix: python: [3.6, 3.7, 3.8, 3.9] steps: - - uses: actions/checkout@v2.3.4 + - uses: actions/checkout@v2.3.5 - name: Set up Python uses: actions/setup-python@v2.2.2 with: From 010116a82436d9724ab7b81388ac38ad4fb1eab2 Mon Sep 17 00:00:00 2001 From: Gustavo Cunha Date: Tue, 19 Oct 2021 08:17:47 +0200 Subject: [PATCH 273/403] Add MinMetricAgg and PercentilesMetricAgg to elasticsearch (#412) --- CHANGELOG.rst | 1 + grafanalib/elasticsearch.py | 67 +++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 81c3bd42..9e289a1c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ x.x.x (TBD) * Added support for colors in stat mapping panel with StatValueMappings & StatRangeMappings * Added missing auto interval properties in Template * Added support for time series panel added in Grafana v8 +* Added MinMetricAgg and PercentilesMetricAgg to Elasticsearch Changes ------- diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index c28d2c15..2b516010 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -431,3 +431,70 @@ class ElasticsearchAlertCondition(AlertCondition): """ target = attr.ib(validator=instance_of(ElasticsearchTarget)) + + +@attr.s +class MinMetricAgg(object): + """An aggregator that provides the min. value among the values. + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-min-aggregation.html + :param field: name of elasticsearch field to provide the maximum for + :param hide: show/hide the metric in the final panel display + :param id: id of the metric + :param inline: script to apply to the data, using '_value' + """ + + field = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=0, validator=instance_of(int)) + hide = attr.ib(default=False, validator=instance_of(bool)) + inline = attr.ib(default="", validator=instance_of(str)) + + def to_json_data(self): + self.settings = {} + + if self.inline: + self.settings['script'] = {'inline': self.inline} + + return { + 'id': str(self.id), + 'hide': self.hide, + 'type': 'min', + 'field': self.field, + 'inlineScript': self.inline, + 'settings': self.settings, + } + + +@attr.s +class PercentilesMetricAgg(object): + """A multi-value metrics aggregation that calculates one or more percentiles over numeric values extracted from the aggregated documents + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-percentile-aggregation.html + :param field: name of elasticsearch field to provide the maximum for + :param hide: show/hide the metric in the final panel display + :param id: id of the metric + :param inline: script to apply to the data, using '_value' + :param percents: list of percentiles, like [95,99] + """ + + field = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=0, validator=instance_of(int)) + hide = attr.ib(default=False, validator=instance_of(bool)) + inline = attr.ib(default="", validator=instance_of(str)) + percents = attr.ib(default=attr.Factory(list)) + settings = attr.ib(default={}) + + def to_json_data(self): + self.settings = {} + + self.settings['percents'] = self.percents + + if self.inline: + self.settings['script'] = {'inline': self.inline} + + return { + 'id': str(self.id), + 'hide': self.hide, + 'type': 'percentiles', + 'field': self.field, + 'inlineScript': self.inline, + 'settings': self.settings, + } From da6dd1aacb1603f01e388e730d01746ce227465b Mon Sep 17 00:00:00 2001 From: Gustavo Cunha Date: Tue, 19 Oct 2021 08:24:45 +0200 Subject: [PATCH 274/403] Allow RowPanel to be collapsed. (#415) To keep current behaviour, default value is False. When you open a dashboard, collapsed rows will not load data automatically. This helps perfomance of dashboards when multiple charts are present. When creating collapsed dashboards, subpanels must go on `panels` options. When creating non-collapsed dashboards, panels goes below the row. Co-authored-by: JamesGibo --- CHANGELOG.rst | 1 + grafanalib/core.py | 3 ++- grafanalib/tests/test_grafanalib.py | 8 ++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9e289a1c..cdcd3150 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ x.x.x (TBD) * Added support for colors in stat mapping panel with StatValueMappings & StatRangeMappings * Added missing auto interval properties in Template * Added support for time series panel added in Grafana v8 +* Changed RowPanel to allow collapsed to be defined on creation * Added MinMetricAgg and PercentilesMetricAgg to Elasticsearch Changes diff --git a/grafanalib/core.py b/grafanalib/core.py index 6564c930..66c3d3a9 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1271,6 +1271,7 @@ class RowPanel(Panel): """ panels = attr.ib(default=attr.Factory(list), validator=instance_of(list)) + collapsed = attr.ib(default=False, validator=instance_of(bool)) def _iter_panels(self): return iter(self.panels) @@ -1282,7 +1283,7 @@ def _map_panels(self, f): def to_json_data(self): return self.panel_json( { - 'collapsed': False, + 'collapsed': self.collapsed, 'panels': self.panels, 'type': ROW_TYPE } diff --git a/grafanalib/tests/test_grafanalib.py b/grafanalib/tests/test_grafanalib.py index eeb28bba..2ca63ef9 100644 --- a/grafanalib/tests/test_grafanalib.py +++ b/grafanalib/tests/test_grafanalib.py @@ -201,3 +201,11 @@ def test_row_panel_show_title(): row = G.RowPanel(title='My title', panels=['a', 'b']).to_json_data() assert row['title'] == 'My title' assert row['panels'][0] == 'a' + + +def test_row_panel_collapsed(): + row = G.RowPanel().to_json_data() + assert row['collapsed'] is False + + row = G.RowPanel(collapsed=True).to_json_data() + assert row['collapsed'] is True From c665f21f107e3ec24f97e1bfa648aa67dfb87184 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Fri, 22 Oct 2021 15:25:40 +0100 Subject: [PATCH 275/403] Add support for State Timeline and new Table panel (#406) * Add state timeline * add v8 panels * Remove fields in table config not used in new tables * Add docstrings for table class * Add note to changelog * fix test * fix test * fix test * fix tests * Add extra warning to changelog about breaking change * Add warning and raise exception if styled columns used --- CHANGELOG.rst | 7 +- grafanalib/core.py | 205 ++++++++++++++++++++-------------- grafanalib/tests/test_core.py | 35 +++--- 3 files changed, 138 insertions(+), 109 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cdcd3150..4239f252 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,8 +8,9 @@ x.x.x (TBD) * Added Discrete panel * Added support for colors in stat mapping panel with StatValueMappings & StatRangeMappings * Added missing auto interval properties in Template -* Added support for time series panel added in Grafana v8 -* Changed RowPanel to allow collapsed to be defined on creation +* Added param to RowPanel to collapse the row +* Added StateTimeline panel which was added in Grafana v8 +* Added support for timeseries panel added in Grafana v8 * Added MinMetricAgg and PercentilesMetricAgg to Elasticsearch Changes @@ -18,6 +19,8 @@ Changes * Refine expectations of is_color_code * Deprecated StatMapping, StatValueMapping & StatRangeMapping * Change YAxis min value default from 0 to None +* Support for Table panel for Grafana v8 may have broken backwards compatibility in old Table panel +* Breaking change, support for styled columns in tables removed, no longer used in Grafana v8 new Table 0.5.14 (2021-09-14) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 66c3d3a9..7f209bf2 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -80,6 +80,7 @@ def to_json_data(self): DISCRETE_TYPE = 'natel-discrete-panel' STAT_TYPE = 'stat' SINGLESTAT_TYPE = 'singlestat' +STATE_TIMELINE_TYPE = 'state-timeline' TABLE_TYPE = 'table' TEXT_TYPE = 'text' ALERTLIST_TYPE = 'alertlist' @@ -1267,9 +1268,10 @@ class RowPanel(Panel): Generates Row panel json structure. :param title: title of the panel - :param panels: list of panels in the row + :param collapsed: set True if row should be collapsed + :param panels: list of panels in the row, only to be used when collapsed=True """ - + collapsed = attr.ib(default=False, validator=instance_of(bool)) panels = attr.ib(default=attr.Factory(list), validator=instance_of(list)) collapsed = attr.ib(default=False, validator=instance_of(bool)) @@ -1941,6 +1943,7 @@ class Stat(Panel): :param decimals: number of decimals to display :param format: defines value units :param graphMode: defines if Grafana will draw graph: keys 'area' 'none' + :param noValue: define the default value if no value is found :param mappings: the list of values to text mappings This should be a list of StatMapping objects https://grafana.com/docs/grafana/latest/panels/field-configuration-options/#value-mapping @@ -1957,6 +1960,7 @@ class Stat(Panel): format = attr.ib(default='none') graphMode = attr.ib(default='area') mappings = attr.ib(default=attr.Factory(list)) + noValue = attr.ib(default='none') orientation = attr.ib(default='auto') reduceCalc = attr.ib(default='mean', type=str) textMode = attr.ib(default='auto') @@ -1971,10 +1975,11 @@ def to_json_data(self): 'decimals': self.decimals, 'mappings': self.mappings, 'thresholds': { - 'mode': 'absolute', + 'mode': ABSOLUTE_TYPE, 'steps': self.thresholds, }, - 'unit': self.format + 'unit': self.format, + 'noValue': self.noValue } }, 'options': { @@ -2417,109 +2422,70 @@ def to_json_data(self): } -def _style_columns(columns): - """Generate a list of column styles given some styled columns. - - The 'Table' object in Grafana separates column definitions from column - style definitions. However, when defining dashboards it can be very useful - to define the style next to the column. This function helps that happen. - - :param columns: A list of (Column, ColumnStyle) pairs. The associated - ColumnStyles must not have a 'pattern' specified. You can also provide - 'None' if you want to use the default styles. - :return: A list of ColumnStyle values that can be used in a Grafana - definition. - """ - new_columns = [] - styles = [] - for column, style in columns: - new_columns.append(column) - if not style: - continue - if style.pattern and style.pattern != column.text: - raise ValueError( - "ColumnStyle pattern (%r) must match the column name (%r) if " - "specified" % (style.pattern, column.text)) - styles.append(attr.evolve(style, pattern=column.text)) - return new_columns, styles - - @attr.s class Table(Panel): """Generates Table panel json structure - Grafana doc on table: https://grafana.com/docs/grafana/latest/features/panels/table_panel/#table-panel + Now supports Grafana v8+ + Grafana doc on table: https://grafana.com/docs/grafana/latest/visualizations/table/ - :param columns: table columns for Aggregations view - :param fontSize: defines value font size - :param pageSize: rows per page (None is unlimited) - :param scroll: scroll the table instead of displaying in full - :param showHeader: show the table header - :param sort: table sorting - :param styles: defines formatting for each column - :param transform: table style + :param align: Align cell contents; auto (default), left, center, right + :param colorMode: Default thresholds + :param columns: Table columns for Aggregations view + :param displayMode: By default, Grafana automatically chooses display settings, you can choose; + color-text, color-background, color-background-solid, gradient-gauge, lcd-gauge, basic, json-view + :param fontSize: Defines value font size + :param filterable: Allow user to filter columns, default False + :param mappings: To assign colors to boolean or string values, use Value mappings + :param showHeader: Show the table header + :param thresholds: List of thresholds """ + align = attr.ib(default='auto', validator=instance_of(str)) + colorMode = attr.ib(default='thresholds', validator=instance_of(str)) columns = attr.ib(default=attr.Factory(list)) + displayMode = attr.ib(default='auto', validator=instance_of(str)) fontSize = attr.ib(default='100%') - pageSize = attr.ib(default=None) - scroll = attr.ib(default=True, validator=instance_of(bool)) + filterable = attr.ib(default=False, validator=instance_of(bool)) + mappings = attr.ib(default=attr.Factory(list)) showHeader = attr.ib(default=True, validator=instance_of(bool)) span = attr.ib(default=6) - sort = attr.ib( - default=attr.Factory(ColumnSort), validator=instance_of(ColumnSort)) - styles = attr.ib() - - transform = attr.ib(default=COLUMNS_TRANSFORM) - - @styles.default - def styles_default(self): - return [ - ColumnStyle( - alias='Time', - pattern='Time', - type=DateColumnStyleType(), - ), - ColumnStyle( - alias='time', - pattern='time', - type=DateColumnStyleType(), - ), - ColumnStyle( - pattern='/.*/', - ), - ] + thresholds = attr.ib(default=attr.Factory(list)) @classmethod def with_styled_columns(cls, columns, styles=None, **kwargs): - """Construct a table where each column has an associated style. - - :param columns: A list of (Column, ColumnStyle) pairs, where the - ColumnStyle is the style for the column and does not have a - pattern set (or the pattern is set to exactly the column name). - The ColumnStyle may also be None. - :param styles: An optional list of extra column styles that will be - appended to the table's list of styles. - :param kwargs: Other parameters to the Table constructor. - :return: A Table. - """ - extraStyles = styles if styles else [] - columns, styles = _style_columns(columns) - return cls(columns=columns, styles=styles + extraStyles, **kwargs) + """Styled columns is not support in Grafana v8 Table""" + print("Error: Styled columns is not support in Grafana v8 Table") + print("Please see https://grafana.com/docs/grafana/latest/visualizations/table/ for more options") + raise NotImplementedError def to_json_data(self): return self.panel_json( { + "color": { + "mode": self.colorMode + }, 'columns': self.columns, 'fontSize': self.fontSize, + 'fieldConfig': { + 'defaults': { + 'custom': { + 'align': self.align, + 'displayMode': self.displayMode, + 'filterable': self.filterable + }, + "thresholds": { + "mode": "absolute", + "steps": self.thresholds + } + } + }, 'hideTimeOverride': self.hideTimeOverride, + 'mappings': self.mappings, 'minSpan': self.minSpan, - 'pageSize': self.pageSize, - 'scroll': self.scroll, - 'showHeader': self.showHeader, - 'sort': self.sort, - 'styles': self.styles, - 'transform': self.transform, + 'options': { + 'showHeader': self.showHeader + }, 'type': TABLE_TYPE, } ) @@ -3285,3 +3251,72 @@ def to_json_data(self): 'type': WORLD_MAP_TYPE } ) + + +@attr.s +class StateTimeline(Panel): + """Generates State Timeline panel json structure + Grafana docs on State Timeline panel: https://grafana.com/docs/grafana/latest/visualizations/state-timeline/ + + :param alignValue: Controls value alignment inside state regions, default left + :param colorMode: Default thresholds + :param fillOpacity: Controls the opacity of state regions, default 0.9 + :param legendDisplayMode: refine how the legend appears, list, table or hidden + :param legendPlacement: bottom or top + :param lineWidth: Controls line width of state regions + :param mappings: To assign colors to boolean or string values, use Value mappings + :param mergeValues: Controls whether Grafana merges identical values if they are next to each other, default True + :param rowHeight: Controls how much space between rows there are. 1 = no space = 0.5 = 50% space + :param showValue: Controls whether values are rendered inside the state regions. Auto will render values if there is sufficient space. + :param tooltipMode: Default single + :param thresholds: Thresholds are used to turn the time series into discrete colored state regions + """ + alignValue = attr.ib(default='left', validator=instance_of(str)) + colorMode = attr.ib(default='thresholds', validator=instance_of(str)) + fillOpacity = attr.ib(default=70, validator=instance_of(int)) + legendDisplayMode = attr.ib(default='list', validator=instance_of(str)) + legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) + lineWidth = attr.ib(default=0, validator=instance_of(int)) + mappings = attr.ib(default=attr.Factory(list)) + mergeValues = attr.ib(default=True, validator=instance_of(bool)) + rowHeight = attr.ib(default=0.9, validator=instance_of(float)) + showValue = attr.ib(default='auto', validator=instance_of(str)) + tooltipMode = attr.ib(default='single', validator=instance_of(str)) + thresholds = attr.ib(default=attr.Factory(list)) + + def to_json_data(self): + return self.panel_json( + { + 'fieldConfig': { + 'defaults': { + 'custom': { + 'lineWidth': self.lineWidth, + 'fillOpacity': self.fillOpacity + }, + 'color': { + 'mode': self.colorMode + }, + 'thresholds': { + 'mode': ABSOLUTE_TYPE, + 'steps': self.thresholds, + }, + 'mappings': self.mappings + }, + 'overrides': [] + }, + 'options': { + 'mergeValues': self.mergeValues, + 'showValue': self.showValue, + 'alignValue': self.alignValue, + 'rowHeight': self.rowHeight, + 'legend': { + 'displayMode': self.legendDisplayMode, + 'placement': self.legendPlacement + }, + 'tooltip': { + 'mode': self.tooltipMode + } + }, + 'type': STATE_TIMELINE_TYPE, + } + ) diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 18cacd1f..13093d82 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -94,28 +94,7 @@ def test_custom_template_dont_override_options(): assert t.to_json_data()['current']['value'] == '1' -def test_table_styled_columns(): - t = G.Table.with_styled_columns( - columns=[ - (G.Column('Foo', 'foo'), G.ColumnStyle()), - (G.Column('Bar', 'bar'), None), - ], - dataSource='some data source', - targets=[ - G.Target(expr='some expr'), - ], - title='table title', - ) - assert t.columns == [ - G.Column('Foo', 'foo'), - G.Column('Bar', 'bar'), - ] - assert t.styles == [ - G.ColumnStyle(pattern='Foo'), - ] - - -def test_table_transformations(): +def test_table(): t = G.Table( dataSource='some data source', targets=[ @@ -502,6 +481,18 @@ def test_worldmap(): assert data['circleMaxSize'] == 11 +def test_stateTimeline(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + stateTimeline = G.StateTimeline(data_source, targets, title, rowHeight=0.7) + data = stateTimeline.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title + assert data['options']['rowHeight'] == 0.7 + + def test_timeseries(): data_source = 'dummy data source' targets = ['dummy_prom_query'] From 33038c058273f481ce763ec32b51e0a8737d3e73 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 22 Oct 2021 17:27:03 +0200 Subject: [PATCH 276/403] Move to main branch Fixes: #414 Signed-off-by: Daniel Holbach --- CHANGELOG.rst | 1 + README.rst | 8 ++++---- docs/CONTRIBUTING.rst | 2 +- docs/getting-started.rst | 2 +- docs/releasing.rst | 6 +++--- grafanalib/formatunits.py | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4239f252..1028c4c0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,6 +21,7 @@ Changes * Change YAxis min value default from 0 to None * Support for Table panel for Grafana v8 may have broken backwards compatibility in old Table panel * Breaking change, support for styled columns in tables removed, no longer used in Grafana v8 new Table +* Move development to ``main`` branch on GitHub. If you have work tracking the ``master`` you will need to update this. 0.5.14 (2021-09-14) ================== diff --git a/README.rst b/README.rst index 8bee7e5a..3c2e6079 100644 --- a/README.rst +++ b/README.rst @@ -17,9 +17,9 @@ How it works ============ Take a look at `the examples directory -`_, +`_, e.g. `this dashboard -`_ +`_ will configure a dashboard with a single row, with one QPS graph broken down by status code and another latency graph showing median and 99th percentile latency. @@ -27,7 +27,7 @@ latency. In the code is a fair bit of repetition here, but once you figure out what works for your needs, you can factor that out. See `our Weave-specific customizations -`_ +`_ for inspiration. You can read the entire grafanlib documentation on `readthedocs.io @@ -47,7 +47,7 @@ Generate the JSON dashboard like so: .. code-block:: console - $ curl -o example.dashboard.py https://raw.githubusercontent.com/weaveworks/grafanalib/master/grafanalib/tests/examples/example.dashboard.py + $ curl -o example.dashboard.py https://raw.githubusercontent.com/weaveworks/grafanalib/main/grafanalib/tests/examples/example.dashboard.py $ generate-dashboard -o frontend.json example.dashboard.py diff --git a/docs/CONTRIBUTING.rst b/docs/CONTRIBUTING.rst index 449247c9..78c68c33 100644 --- a/docs/CONTRIBUTING.rst +++ b/docs/CONTRIBUTING.rst @@ -78,4 +78,4 @@ Filing a bug .. _`CHANGELOG`: ../CHANGELOG.rst .. _`attr.Factory`: http://www.attrs.org/en/stable/api.html#attr.Factory .. _`hypothesis`: http://hypothesis.works/ -.. _`core.py`: https://github.com/weaveworks/grafanalib/blob/master/grafanalib/core.py +.. _`core.py`: https://github.com/weaveworks/grafanalib/blob/main/grafanalib/core.py diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 2dd6db64..03c94f50 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -29,7 +29,7 @@ percentile latency: There is a fair bit of repetition here, but once you figure out what works for your needs, you can factor that out. See `our Weave-specific customizations -`_ +`_ for inspiration. Generating dashboards diff --git a/docs/releasing.rst b/docs/releasing.rst index cf364931..21af9783 100644 --- a/docs/releasing.rst +++ b/docs/releasing.rst @@ -6,8 +6,8 @@ Pre-release ----------- * Pick a new version number (e.g. ``X.Y.Z``) -* Update `CHANGELOG `_ with that number -* Update `setup.py `_ with that number +* Update `CHANGELOG `_ with that number +* Update `setup.py `_ with that number Smoke-testing ------------- @@ -19,7 +19,7 @@ Smoke-testing $ python setup.py install --user * Check ``~/.local/bin/generate-dashboard`` for the update version. -* Try the example on `README `_. +* Try the example on `README `_. Releasing --------- diff --git a/grafanalib/formatunits.py b/grafanalib/formatunits.py index 0b8ad1ec..51022113 100644 --- a/grafanalib/formatunits.py +++ b/grafanalib/formatunits.py @@ -1,6 +1,6 @@ """ Grafana unit formats -(https://github.com/grafana/grafana/blob/master/packages/grafana-data/src/valueFormats/categories.ts) +(https://github.com/grafana/grafana/blob/main/packages/grafana-data/src/valueFormats/categories.ts) To use: from grafanalib import formatunits as UNITS From 903c654a0798aace0627dab3e0f374d28d7756f4 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Fri, 22 Oct 2021 17:30:00 +0200 Subject: [PATCH 277/403] add python 3.10 to tests Signed-off-by: Daniel Holbach --- .github/workflows/run-tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 8cb22cda..4b8b8685 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-18.04 strategy: matrix: - python: [3.6, 3.7, 3.8, 3.9] + python: ['3.6', '3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v2.3.5 - name: Set up Python From d9793a8510023d870d8a75a1ddefbb5785824a4b Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 26 Oct 2021 09:17:16 +0100 Subject: [PATCH 278/403] Add support for pie chart v2 (#420) --- CHANGELOG.rst | 1 + grafanalib/core.py | 77 +++++++++++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 11 +++++ 3 files changed, 89 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1028c4c0..3d8f2860 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ x.x.x (TBD) * Added StateTimeline panel which was added in Grafana v8 * Added support for timeseries panel added in Grafana v8 * Added MinMetricAgg and PercentilesMetricAgg to Elasticsearch +* Added support for Pie Chart v2 from Grafana v8 Changes ------- diff --git a/grafanalib/core.py b/grafanalib/core.py index 7f209bf2..3f5a477d 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -92,6 +92,7 @@ def to_json_data(self): STATUSMAP_TYPE = 'flant-statusmap-panel' SVG_TYPE = 'marcuscalidus-svg-panel' PIE_CHART_TYPE = 'grafana-piechart-panel' +PIE_CHART_V2_TYPE = 'piechart' TIMESERIES_TYPE = 'timeseries' WORLD_MAP_TYPE = 'grafana-worldmap-panel' @@ -2923,6 +2924,9 @@ def to_json_data(self): @attr.s class PieChart(Panel): """Generates Pie Chart panel json structure + + This panel was deprecated in Grafana 8.0, please use PieChartv2 instead + Grafana doc on Pie Chart: https://grafana.com/grafana/plugins/grafana-piechart-panel :param aliasColors: dictionary of color overrides @@ -2947,6 +2951,7 @@ class PieChart(Panel): thresholds = attr.ib(default="") def to_json_data(self): + print('PieChart panel was deprecated in Grafana 8.0, please use PieChartv2 instead') return self.panel_json( { 'aliasColors': self.aliasColors, @@ -2971,6 +2976,78 @@ def to_json_data(self): ) +@attr.s +class PieChartv2(Panel): + """Generates Pie Chart panel json structure + Grafana docs on Pie Chart: https://grafana.com/docs/grafana/latest/visualizations/pie-chart-panel/ + + :param custom: Custom overides + :param colorMode: Color mode + palette-classic (Default), + :param legendDisplayMode: Display mode of legend: list, table or hidden + :param legendPlacement: Location of the legend in the panel: bottom or right + :param legendValues: List of value to be shown in legend eg. ['value', 'percent'] + :param mappings: To assign colors to boolean or string values, use Value mappings + :param overrides: Overrides + :param pieType: Pie chart type + pie (Default), donut + :param reduceOptionsCalcs: Reducer function / calculation + :param reduceOptionsFields: Fields that should be included in the panel + :param reduceOptionsValues: Calculate a single value per column or series or show each row + :param tooltipMode: Tooltip mode + single (Default), multi, none + :param unit: units + """ + + custom = attr.ib(default={}, validator=instance_of(dict)) + colorMode = attr.ib(default='palette-classic', validator=instance_of(str)) + legendDisplayMode = attr.ib(default='list', validator=instance_of(str)) + legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) + legendValues = attr.ib(default=[], validator=instance_of(list)) + mappings = attr.ib(default=attr.Factory(list)) + overrides = attr.ib(default=[], validator=instance_of(list)) + pieType = attr.ib(default='pie', validator=instance_of(str)) + reduceOptionsCalcs = attr.ib(default=['lastNotNull'], validator=instance_of(list)) + reduceOptionsFields = attr.ib(default='', validator=instance_of(str)) + reduceOptionsValues = attr.ib(default=False, validator=instance_of(bool)) + tooltipMode = attr.ib(default='single', validator=instance_of(str)) + unit = attr.ib(default='', validator=instance_of(str)) + + def to_json_data(self): + return self.panel_json( + { + 'fieldConfig': { + 'defaults': { + 'color': { + 'mode': self.colorMode + }, + 'custom': self.custom, + 'mappings': self.mappings, + 'unit': self.unit, + }, + 'overrides': self.overrides, + }, + 'options': { + 'reduceOptions': { + 'values': self.reduceOptionsValues, + 'calcs': self.reduceOptionsCalcs, + 'fields': self.reduceOptionsFields + }, + 'pieType': self.pieType, + 'tooltip': { + 'mode': self.tooltipMode + }, + 'legend': { + 'displayMode': self.legendDisplayMode, + 'placement': self.legendPlacement, + 'values': self.legendValues + }, + }, + 'type': PIE_CHART_V2_TYPE, + } + ) + + @attr.s class DashboardList(Panel): """Generates Dashboard list panel json structure diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 13093d82..ff8569cf 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -502,3 +502,14 @@ def test_timeseries(): assert data['targets'] == targets assert data['datasource'] == data_source assert data['title'] == title + + +def test_pieChartv2(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + pie = G.PieChartv2(data_source, targets, title) + data = pie.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title From 9cde34a177b80a25089c0bf579ecc88d1d6bf22d Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 26 Oct 2021 09:22:36 +0100 Subject: [PATCH 279/403] Add support for news panel (#419) * Add support for news panel * Fix linting --- CHANGELOG.rst | 1 + grafanalib/core.py | 27 +++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 9 +++++++++ 3 files changed, 37 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3d8f2860..c150919d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -12,6 +12,7 @@ x.x.x (TBD) * Added StateTimeline panel which was added in Grafana v8 * Added support for timeseries panel added in Grafana v8 * Added MinMetricAgg and PercentilesMetricAgg to Elasticsearch +* Added support for News panel * Added support for Pie Chart v2 from Grafana v8 Changes diff --git a/grafanalib/core.py b/grafanalib/core.py index 3f5a477d..20489645 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -95,6 +95,7 @@ def to_json_data(self): PIE_CHART_V2_TYPE = 'piechart' TIMESERIES_TYPE = 'timeseries' WORLD_MAP_TYPE = 'grafana-worldmap-panel' +NEWS_TYPE = 'news' DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -3397,3 +3398,29 @@ def to_json_data(self): 'type': STATE_TIMELINE_TYPE, } ) + + +@attr.s +class News(Panel): + """Generates News panel json structure + Grafana docs on State Timeline panel: https://grafana.com/docs/grafana/next/visualizations/news-panel/ + + :param feedUrl: URL to query, only RSS feed formats are supported (not Atom). + :param showImage: Controls if the news item social (og:image) image is shown above text content + :param useProxy: If the feed is unable to connect, consider a CORS proxy + """ + feedUrl = attr.ib(default='', validator=instance_of(str)) + showImage = attr.ib(default=True, validator=instance_of(bool)) + useProxy = attr.ib(default=False, validator=instance_of(bool)) + + def to_json_data(self): + return self.panel_json( + { + 'options': { + 'feedUrl': self.feedUrl, + 'showImage': self.showImage, + 'useProxy': self.useProxy + }, + 'type': NEWS_TYPE, + } + ) diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index ff8569cf..e9005baa 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -504,6 +504,15 @@ def test_timeseries(): assert data['title'] == title +def test_news(): + title = 'dummy title' + feedUrl = "www.example.com" + news = G.News(title=title, feedUrl=feedUrl) + data = news.to_json_data() + assert data['options']['feedUrl'] == feedUrl + assert data['title'] == title + + def test_pieChartv2(): data_source = 'dummy data source' targets = ['dummy_prom_query'] From 62d1c7577c8fad9fe0c2e934fd2bb8beb3f7518c Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 26 Oct 2021 11:55:11 +0100 Subject: [PATCH 280/403] Prep for release (#421) --- CHANGELOG.rst | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c150919d..1c7a1277 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,7 @@ Changelog ========= -x.x.x (TBD) +0.6.0 (2021-10-26) =================== * Added Discrete panel diff --git a/setup.py b/setup.py index 18c841a6..9238e347 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.5.14', + version='0.6.0', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 94175f42d1490cb0ed9bed6228b9ed5bb4788461 Mon Sep 17 00:00:00 2001 From: Puneeth Date: Fri, 29 Oct 2021 13:54:53 +0200 Subject: [PATCH 281/403] Add more attributes to Logs panel (#410) --- CHANGELOG.rst | 11 +++++++++++ grafanalib/core.py | 16 ++++++++++++++-- grafanalib/tests/test_core.py | 4 ++++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1c7a1277..32b3d0bc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,17 @@ Changelog ========= +x.x.x (TBD) +================== + +* Added missing attributes to the Logs panel +* ... + +Changes +------- + +* ... + 0.6.0 (2021-10-26) =================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 20489645..b4100576 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -3098,15 +3098,23 @@ class Logs(Panel): Grafana doc on Logs panel: https://grafana.com/docs/grafana/latest/panels/visualizations/logs-panel/ :param showLabels: Show or hide the unique labels column, which shows only non-common labels + :param showCommonLabels: Show or hide the common labels. :param showTime: Show or hide the log timestamp column :param wrapLogMessages: Toggle line wrapping :param sortOrder: Display results in 'Descending' or 'Ascending' time order. The default is Descending, showing the newest logs first. + :param dedupStrategy: One of none, exact, numbers, signature. Default is none + :param enableLogDetails: Set this to True to see the log details view for each log row. + :param prettifyLogMessage: Set this to true to pretty print all JSON logs. This setting does not affect logs in any format other than JSON. """ showLabels = attr.ib(default=False, validator=instance_of(bool)) + showCommonLabels = attr.ib(default=False, validator=instance_of(bool)) showTime = attr.ib(default=False, validator=instance_of(bool)) - wrapLogMessages = attr.ib(default=False, validator=instance_of(bool)) + wrapLogMessage = attr.ib(default=False, validator=instance_of(bool)) sortOrder = attr.ib(default='Descending', validator=instance_of(str)) + dedupStrategy = attr.ib(default='none', validator=instance_of(str)) + enableLogDetails = attr.ib(default=False, validator=instance_of(bool)) + prettifyLogMessage = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): return self.panel_json( @@ -3119,9 +3127,13 @@ def to_json_data(self): }, 'options': { 'showLabels': self.showLabels, + 'showCommonLabels': self.showCommonLabels, 'showTime': self.showTime, + 'wrapLogMessage': self.wrapLogMessage, 'sortOrder': self.sortOrder, - 'wrapLogMessage': self.wrapLogMessages + 'dedupStrategy': self.dedupStrategy, + 'enableLogDetails': self.enableLogDetails, + 'prettifyLogMessage': self.prettifyLogMessage }, 'type': LOGS_TYPE, } diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index e9005baa..69f14caa 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -331,9 +331,13 @@ def test_logs_panel(): assert data['datasource'] == data_source assert data['title'] == title assert data['options']['showLabels'] is False + assert data['options']['showCommonLabels'] is False assert data['options']['showTime'] is False assert data['options']['wrapLogMessage'] is False assert data['options']['sortOrder'] == 'Descending' + assert data['options']['dedupStrategy'] == 'none' + assert data['options']['enableLogDetails'] is False + assert data['options']['prettifyLogMessage'] is False def test_notification(): From f2ae30c59a48eb8e900f8c750ce2a36c4a0e2bb4 Mon Sep 17 00:00:00 2001 From: Puneeth Date: Fri, 29 Oct 2021 17:02:18 +0200 Subject: [PATCH 282/403] Revert to wrapLogMessages (#424) --- grafanalib/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index b4100576..a355465b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -3110,7 +3110,7 @@ class Logs(Panel): showLabels = attr.ib(default=False, validator=instance_of(bool)) showCommonLabels = attr.ib(default=False, validator=instance_of(bool)) showTime = attr.ib(default=False, validator=instance_of(bool)) - wrapLogMessage = attr.ib(default=False, validator=instance_of(bool)) + wrapLogMessages = attr.ib(default=False, validator=instance_of(bool)) sortOrder = attr.ib(default='Descending', validator=instance_of(str)) dedupStrategy = attr.ib(default='none', validator=instance_of(str)) enableLogDetails = attr.ib(default=False, validator=instance_of(bool)) @@ -3129,7 +3129,7 @@ def to_json_data(self): 'showLabels': self.showLabels, 'showCommonLabels': self.showCommonLabels, 'showTime': self.showTime, - 'wrapLogMessage': self.wrapLogMessage, + 'wrapLogMessage': self.wrapLogMessages, 'sortOrder': self.sortOrder, 'dedupStrategy': self.dedupStrategy, 'enableLogDetails': self.enableLogDetails, From b7e8e0f6a817d9a3f11173352f75c9a6ff90adb6 Mon Sep 17 00:00:00 2001 From: Puneeth Date: Mon, 1 Nov 2021 11:25:19 +0100 Subject: [PATCH 283/403] Add Cloudwatch Logs Insights Target (#423) --- CHANGELOG.rst | 1 + grafanalib/cloudwatch.py | 44 +++++++++++++++++++++++++++++ grafanalib/tests/test_cloudwatch.py | 41 +++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 32b3d0bc..32630534 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ x.x.x (TBD) ================== * Added missing attributes to the Logs panel +* Added Cloudwatch Logs Insights Target * ... Changes diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py index 60b51d81..dc743747 100644 --- a/grafanalib/cloudwatch.py +++ b/grafanalib/cloudwatch.py @@ -58,3 +58,47 @@ def to_json_data(self): "statistics": self.statistics, "hide": self.hide, } + + +@attr.s +class CloudwatchLogsInsightsTarget(object): + """ + Generates Cloudwatch Logs Insights target JSON structure. + + Grafana docs on using Cloudwatch: + https://grafana.com/docs/grafana/latest/datasources/cloudwatch/ + + AWS docs on Cloudwatch Logs Insights: + https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html + + :param expression: Cloudwatch Logs Insights expressions + :param id: unique id + :param logGroupNames: List of Cloudwatch log groups to query + :param namespace: Cloudwatch namespace + :param refId: target reference id + :param region: Cloudwatch region + :param statsGroups: Cloudwatch statsGroups + :param hide: controls if given metric is displayed on visualization + """ + expression = attr.ib(default="") + id = attr.ib(default="") + logGroupNames = attr.ib(default=[], validator=instance_of(list)) + namespace = attr.ib(default="") + refId = attr.ib(default="") + region = attr.ib(default="default") + statsGroups = attr.ib(default=[], validator=instance_of(list)) + hide = attr.ib(default=False, validator=instance_of(bool)) + + def to_json_data(self): + + return { + "expression": self.expression, + "id": self.id, + "logGroupNames": self.logGroupNames, + "namespace": self.namespace, + "queryMode": "Logs", + "refId": self.refId, + "region": self.region, + "statsGroups": self.statsGroups, + "hide": self.hide, + } diff --git a/grafanalib/tests/test_cloudwatch.py b/grafanalib/tests/test_cloudwatch.py index b9e91bbf..b35cdf6c 100644 --- a/grafanalib/tests/test_cloudwatch.py +++ b/grafanalib/tests/test_cloudwatch.py @@ -23,3 +23,44 @@ def test_serialization_cloudwatch_metrics_target(): stream = StringIO() _gen.write_dashboard(graph, stream) assert stream.getvalue() != '' + + +def test_serialization_cloudwatch_logs_insights_target(): + """Serializing a graph doesn't explode.""" + graph = G.Logs( + title="Lambda Duration", + dataSource="Cloudwatch data source", + targets=[ + C.CloudwatchLogsInsightsTarget(), + ], + id=1, + wrapLogMessages=True + ) + stream = StringIO() + _gen.write_dashboard(graph, stream) + assert stream.getvalue() != '' + + +def test_cloudwatch_logs_insights_target(): + """Test Cloudwatch Logs Insights target""" + cloudwatch_logs_insights_expression = "fields @timestamp, @xrayTraceId, @message | filter @message like /^(?!.*(START|END|REPORT|LOGS|EXTENSION)).*$/ | sort @timestamp desc" + ref_id = "A" + log_group_names = ["/aws/lambda/foo", "/aws/lambda/bar"] + + target = C.CloudwatchLogsInsightsTarget( + expression=cloudwatch_logs_insights_expression, + logGroupNames=log_group_names, + refId=ref_id + ) + + data = target.to_json_data() + + assert data["expression"] == cloudwatch_logs_insights_expression + assert data["id"] == "" + assert data["logGroupNames"] == log_group_names + assert data["namespace"] == "" + assert data["queryMode"] == "Logs" + assert data["refId"] == ref_id + assert data["region"] == "default" + assert data["statsGroups"] == [] + assert data["hide"] is False From 5e1f0ec493c0257a729e6bd0a3d2e7e3dcdd4cf1 Mon Sep 17 00:00:00 2001 From: Puneeth Date: Mon, 1 Nov 2021 11:29:48 +0100 Subject: [PATCH 284/403] Add missing overrides to timeseries visualization (#425) --- grafanalib/core.py | 4 +++- grafanalib/tests/test_core.py | 36 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index a355465b..3509bf7b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1505,6 +1505,7 @@ class TimeSeries(Panel): linear (Default), smooth, stepBefore, stepAfter :param lineWidth: line width, default 1 :param mappings: To assign colors to boolean or string values, use Value mappings + :param overrides: To override the base characteristics of certain timeseries data :param pointSize: point size, default 5 :param scaleDistributionType: axis scale linear or log :param scaleDistributionLog: Base of if logarithmic scale type set, default 2 @@ -1530,6 +1531,7 @@ class TimeSeries(Panel): lineInterpolation = attr.ib(default='linear', validator=instance_of(str)) lineWidth = attr.ib(default=1, validator=instance_of(int)) mappings = attr.ib(default=attr.Factory(list)) + overrides = attr.ib(default=attr.Factory(list)) pointSize = attr.ib(default=5, validator=instance_of(int)) scaleDistributionType = attr.ib(default='linear', validator=instance_of(str)) scaleDistributionLog = attr.ib(default=2, validator=instance_of(int)) @@ -1578,7 +1580,7 @@ def to_json_data(self): }, 'unit': self.unit }, - 'overrides': [] + 'overrides': self.overrides }, 'options': { 'legend': { diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 69f14caa..fe4c8bd1 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -506,6 +506,42 @@ def test_timeseries(): assert data['targets'] == targets assert data['datasource'] == data_source assert data['title'] == title + assert data['fieldConfig']['overrides'] == [] + + +def test_timeseries_with_overrides(): + data_source = 'dummy data source' + targets = ['dummy_prom_query'] + title = 'dummy title' + overrides = [ + { + "matcher": { + "id": "byName", + "options": "min" + }, + "properties": [ + { + "id": "custom.fillBelowTo", + "value": "min" + }, + { + "id": "custom.lineWidth", + "value": 0 + } + ] + } + ] + timeseries = G.TimeSeries( + dataSource=data_source, + targets=targets, + title=title, + overrides=overrides, + ) + data = timeseries.to_json_data() + assert data['targets'] == targets + assert data['datasource'] == data_source + assert data['title'] == title + assert data['fieldConfig']['overrides'] == overrides def test_news(): From aa17844049ff22b01fcbe3eee03918b15ef59a90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Nov 2021 12:19:35 +0000 Subject: [PATCH 285/403] Bump actions/checkout from 2.3.5 to 2.4.0 (#427) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.3.5 to 2.4.0. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.3.5...v2.4.0) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 958795d0..b191927d 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -7,7 +7,7 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.3.5 + - uses: actions/checkout@v2.4.0 - name: Set up Python uses: actions/setup-python@v2.2.2 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 8ff25e0a..4fb7f28b 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -7,7 +7,7 @@ jobs: name: Build and publish Python 🐍 distributions 📦 to PyPI runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2.3.5 + - uses: actions/checkout@v2.4.0 - name: Set up Python 3.7 uses: actions/setup-python@v2.2.2 with: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 4b8b8685..8bff6150 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: matrix: python: ['3.6', '3.7', '3.8', '3.9', '3.10'] steps: - - uses: actions/checkout@v2.3.5 + - uses: actions/checkout@v2.4.0 - name: Set up Python uses: actions/setup-python@v2.2.2 with: From cb8a8f5690d6793a15927b0ac7da94dad41f9c62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 8 Nov 2021 12:19:45 +0000 Subject: [PATCH 286/403] Bump lycheeverse/lychee-action from 1.0.9 to 1.1.0 (#426) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.0.9 to 1.1.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.0.9...v1.1.0) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index b191927d..f38eab24 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.0.9 + uses: lycheeverse/lychee-action@v1.1.0 with: args: --verbose **/*.html - name: Fail if there were link errors From 141efa66c4c588dc2def65f938e7a1bfd4e89dfa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Nov 2021 12:02:01 +0000 Subject: [PATCH 287/403] Bump sphinx from 4.2.0 to 4.3.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.2.0...v4.3.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index b17688d5..b65fa733 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.2.0 +sphinx == 4.3.0 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From bbe0b6653943ee1fc80a913925a576b13f9675ac Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Thu, 18 Nov 2021 08:42:11 +0000 Subject: [PATCH 288/403] Add overrides config to panels (#430) --- CHANGELOG.rst | 1 + grafanalib/core.py | 28 +++++++++++++++++++++------- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 32630534..00454887 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ x.x.x (TBD) * Added missing attributes to the Logs panel * Added Cloudwatch Logs Insights Target +* Added overrides to panels * ... Changes diff --git a/grafanalib/core.py b/grafanalib/core.py index 3509bf7b..d9e247cb 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1952,6 +1952,7 @@ class Stat(Panel): This should be a list of StatMapping objects https://grafana.com/docs/grafana/latest/panels/field-configuration-options/#value-mapping :param orientation: Stacking direction in case of multiple series or fields: keys 'auto' 'horizontal' 'vertical' + :param overrides: To override the base characteristics of certain timeseries data :param reduceCalc: algorithm for reduction to a single value: keys 'mean' 'lastNotNull' 'last' 'first' 'firstNotNull' 'min' 'max' 'sum' 'total' :param textMode: define Grafana will show name or value: keys: 'auto' 'name' 'none' 'value' 'value_and_name' @@ -1966,6 +1967,7 @@ class Stat(Panel): mappings = attr.ib(default=attr.Factory(list)) noValue = attr.ib(default='none') orientation = attr.ib(default='auto') + overrides = attr.ib(default=attr.Factory(list)) reduceCalc = attr.ib(default='mean', type=str) textMode = attr.ib(default='auto') thresholds = attr.ib(default="") @@ -1984,7 +1986,8 @@ def to_json_data(self): }, 'unit': self.format, 'noValue': self.noValue - } + }, + 'overrides': self.overrides }, 'options': { 'textMode': self.textMode, @@ -2441,6 +2444,7 @@ class Table(Panel): :param fontSize: Defines value font size :param filterable: Allow user to filter columns, default False :param mappings: To assign colors to boolean or string values, use Value mappings + :param overrides: To override the base characteristics of certain data :param showHeader: Show the table header :param thresholds: List of thresholds """ @@ -2452,6 +2456,7 @@ class Table(Panel): fontSize = attr.ib(default='100%') filterable = attr.ib(default=False, validator=instance_of(bool)) mappings = attr.ib(default=attr.Factory(list)) + overrides = attr.ib(default=attr.Factory(list)) showHeader = attr.ib(default=True, validator=instance_of(bool)) span = attr.ib(default=6) thresholds = attr.ib(default=attr.Factory(list)) @@ -2482,7 +2487,8 @@ def to_json_data(self): "mode": "absolute", "steps": self.thresholds } - } + }, + 'overrides': self.overrides }, 'hideTimeOverride': self.hideTimeOverride, 'mappings': self.mappings, @@ -2934,18 +2940,20 @@ class PieChart(Panel): :param aliasColors: dictionary of color overrides :param format: defines value units + :param legendType: defines where the legend position + :param overrides: To override the base characteristics of certain data :param pieType: defines the shape of the pie chart (pie or donut) :param percentageDecimals: Number of decimal places to show if percentages shown in legned :param showLegend: defines if the legend should be shown :param showLegendValues: defines if the legend should show values :param showLegendPercentage: Show percentages in the legend - :param legendType: defines where the legend position :param thresholds: defines thresholds """ aliasColors = attr.ib(default=attr.Factory(dict)) format = attr.ib(default='none') legendType = attr.ib(default='Right side') + overrides = attr.ib(default=attr.Factory(list)) pieType = attr.ib(default='pie') percentageDecimals = attr.ib(default=0, validator=instance_of(int)) showLegend = attr.ib(default=True) @@ -2965,7 +2973,7 @@ def to_json_data(self): 'defaults': { 'custom': {}, }, - 'overrides': [] + 'overrides': self.overrides }, 'legend': { 'show': self.showLegend, @@ -3064,6 +3072,7 @@ class DashboardList(Panel): :param maxItems: Sets the maximum number of items to list per section :param searchQuery: Enter the query you want to search by :param searchTags: List of tags you want to search by + :param overrides: To override the base characteristics of certain data """ showHeadings = attr.ib(default=True, validator=instance_of(bool)) showSearch = attr.ib(default=False, validator=instance_of(bool)) @@ -3072,6 +3081,7 @@ class DashboardList(Panel): maxItems = attr.ib(default=10, validator=instance_of(int)) searchQuery = attr.ib(default='', validator=instance_of(str)) searchTags = attr.ib(default=attr.Factory(list), validator=instance_of(list)) + overrides = attr.ib(default=attr.Factory(list)) def to_json_data(self): return self.panel_json( @@ -3080,7 +3090,7 @@ def to_json_data(self): 'defaults': { 'custom': {}, }, - 'overrides': [] + 'overrides': self.overrides }, 'headings': self.showHeadings, 'search': self.showSearch, @@ -3107,6 +3117,7 @@ class Logs(Panel): showing the newest logs first. :param dedupStrategy: One of none, exact, numbers, signature. Default is none :param enableLogDetails: Set this to True to see the log details view for each log row. + :param overrides: To override the base characteristics of certain data :param prettifyLogMessage: Set this to true to pretty print all JSON logs. This setting does not affect logs in any format other than JSON. """ showLabels = attr.ib(default=False, validator=instance_of(bool)) @@ -3116,6 +3127,7 @@ class Logs(Panel): sortOrder = attr.ib(default='Descending', validator=instance_of(str)) dedupStrategy = attr.ib(default='none', validator=instance_of(str)) enableLogDetails = attr.ib(default=False, validator=instance_of(bool)) + overrides = attr.ib(default=attr.Factory(list)) prettifyLogMessage = attr.ib(default=False, validator=instance_of(bool)) def to_json_data(self): @@ -3125,7 +3137,7 @@ def to_json_data(self): 'defaults': { 'custom': {}, }, - 'overrides': [] + 'overrides': self.overrides }, 'options': { 'showLabels': self.showLabels, @@ -3357,6 +3369,7 @@ class StateTimeline(Panel): :param legendPlacement: bottom or top :param lineWidth: Controls line width of state regions :param mappings: To assign colors to boolean or string values, use Value mappings + :param overrides: To override the base characteristics of certain data :param mergeValues: Controls whether Grafana merges identical values if they are next to each other, default True :param rowHeight: Controls how much space between rows there are. 1 = no space = 0.5 = 50% space :param showValue: Controls whether values are rendered inside the state regions. Auto will render values if there is sufficient space. @@ -3370,6 +3383,7 @@ class StateTimeline(Panel): legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) lineWidth = attr.ib(default=0, validator=instance_of(int)) mappings = attr.ib(default=attr.Factory(list)) + overrides = attr.ib(default=attr.Factory(list)) mergeValues = attr.ib(default=True, validator=instance_of(bool)) rowHeight = attr.ib(default=0.9, validator=instance_of(float)) showValue = attr.ib(default='auto', validator=instance_of(str)) @@ -3394,7 +3408,7 @@ def to_json_data(self): }, 'mappings': self.mappings }, - 'overrides': [] + 'overrides': self.overrides }, 'options': { 'mergeValues': self.mergeValues, From cfbbc0e434e352c0286df4a5fc626d57cb9893bc Mon Sep 17 00:00:00 2001 From: rjulius23 Date: Thu, 18 Nov 2021 14:43:16 +0100 Subject: [PATCH 289/403] Add SQL Target (#429) * Add SQL Target This change is supposed to make it possible to define SQL queries as well for targets so Data Sources like MySQL, PostgreSQL can work too. * Update CHANGELOG.rst * Bump version * Update grafanalib/core.py * Fix docstrings and pep8 Fix missing blank line and add docstrings. * Add unit test for SqlTarget Test that the necessary parameters are set in the json if SqlTarget is used. * Fix tests and add example dashboard Co-authored-by: Gyula Halmos --- CHANGELOG.rst | 10 +++++ grafanalib/core.py | 21 ++++++++++ .../examples/example.dashboard-with-sql.py | 34 +++++++++++++++++ grafanalib/tests/test_core.py | 38 ++++++++++++------- setup.py | 2 +- 5 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 grafanalib/tests/examples/example.dashboard-with-sql.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 00454887..81f0f21a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,16 @@ Changes * ... +0.6.1 (TBD) +================== + +* Added new SqlTarget to core to be able to define SQL queries as well + +Changes +------- + +* Add SqlTarget + 0.6.0 (2021-10-26) =================== diff --git a/grafanalib/core.py b/grafanalib/core.py index d9e247cb..7d35b02a 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -427,6 +427,27 @@ def to_json_data(self): } +@attr.s +class SqlTarget(Target): + """ + Metric target to support SQL queries + """ + + rawSql = attr.ib(default="") + rawQuery = attr.ib(default=True) + + def to_json_data(self): + """Override the Target to_json_data to add additional fields. + rawSql: this will contain the actual SQL queries + rawQuery: this is set to True by default as in case of False + the rawSql would be unused + """ + super_json = super(SqlTarget, self).to_json_data() + super_json["rawSql"] = self.rawSql + super_json["rawQuery"] = self.rawQuery + return super_json + + @attr.s class Tooltip(object): diff --git a/grafanalib/tests/examples/example.dashboard-with-sql.py b/grafanalib/tests/examples/example.dashboard-with-sql.py new file mode 100644 index 00000000..e18a4180 --- /dev/null +++ b/grafanalib/tests/examples/example.dashboard-with-sql.py @@ -0,0 +1,34 @@ +from grafanalib.core import ( + Dashboard, + Graph, + GridPos, + OPS_FORMAT, + RowPanel, + SHORT_FORMAT, + SqlTarget, + YAxes, + YAxis, +) + + +dashboard = Dashboard( + title="Random stats from SQL DB", + panels=[ + RowPanel(title="New row", gridPos=GridPos(h=1, w=24, x=0, y=8)), + Graph( + title="Some SQL Queries", + dataSource="Your SQL Source", + targets=[ + SqlTarget( + rawSql='SELECT date as "time", metric FROM example WHERE $__timeFilter("time")', + refId="A", + ), + ], + yAxes=YAxes( + YAxis(format=OPS_FORMAT), + YAxis(format=SHORT_FORMAT), + ), + gridPos=GridPos(h=8, w=24, x=0, y=9), + ), + ], +).auto_panel_ids() diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index fe4c8bd1..b1ab24d0 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -538,27 +538,39 @@ def test_timeseries_with_overrides(): overrides=overrides, ) data = timeseries.to_json_data() - assert data['targets'] == targets - assert data['datasource'] == data_source - assert data['title'] == title - assert data['fieldConfig']['overrides'] == overrides + assert data["targets"] == targets + assert data["datasource"] == data_source + assert data["title"] == title + assert data["fieldConfig"]["overrides"] == overrides def test_news(): - title = 'dummy title' + title = "dummy title" feedUrl = "www.example.com" news = G.News(title=title, feedUrl=feedUrl) data = news.to_json_data() - assert data['options']['feedUrl'] == feedUrl - assert data['title'] == title + assert data["options"]["feedUrl"] == feedUrl + assert data["title"] == title def test_pieChartv2(): - data_source = 'dummy data source' - targets = ['dummy_prom_query'] - title = 'dummy title' + data_source = "dummy data source" + targets = ["dummy_prom_query"] + title = "dummy title" pie = G.PieChartv2(data_source, targets, title) data = pie.to_json_data() - assert data['targets'] == targets - assert data['datasource'] == data_source - assert data['title'] == title + assert data["targets"] == targets + assert data["datasource"] == data_source + assert data["title"] == title + + +def test_sql_target(): + t = G.Table( + dataSource="some data source", + targets=[ + G.SqlTarget(rawSql="SELECT * FROM example"), + ], + title="table title", + ) + assert t.to_json_data()["targets"][0].rawQuery is True + assert t.to_json_data()["targets"][0].rawSql == "SELECT * FROM example" diff --git a/setup.py b/setup.py index 9238e347..5b850f73 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.6.0', + version='0.6.1', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From ee0fd28c3219b60dc07601bd37d6d70e246be0ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Nov 2021 12:03:19 +0000 Subject: [PATCH 290/403] Bump actions/setup-python from 2.2.2 to 2.3.0 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.2.2 to 2.3.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.2.2...v2.3.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index f38eab24..d093d11c 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Set up Python - uses: actions/setup-python@v2.2.2 + uses: actions/setup-python@v2.3.0 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 4fb7f28b..6e15ffc5 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Set up Python 3.7 - uses: actions/setup-python@v2.2.2 + uses: actions/setup-python@v2.3.0 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 8bff6150..fe86f41d 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Set up Python - uses: actions/setup-python@v2.2.2 + uses: actions/setup-python@v2.3.0 with: python-version: ${{ matrix.python }} - name: Run tests From d1ba692e4aa09ed957ee34bba461e5777435577a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Nov 2021 12:03:21 +0000 Subject: [PATCH 291/403] Bump lycheeverse/lychee-action from 1.1.0 to 1.1.1 Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.1.0 to 1.1.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.1.0...v1.1.1) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index f38eab24..5f0a3864 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.1.0 + uses: lycheeverse/lychee-action@v1.1.1 with: args: --verbose **/*.html - name: Fail if there were link errors From ade0e0e27fa302fb327a7999c6fd4b505269aedf Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Tue, 23 Nov 2021 17:29:35 +0000 Subject: [PATCH 292/403] Prep for 0.6.1 release (#433) --- CHANGELOG.rst | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 81f0f21a..e82752f5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,28 +2,14 @@ Changelog ========= -x.x.x (TBD) +0.6.1 (TBD) ================== +* Added new SqlTarget to core to be able to define SQL queries as well * Added missing attributes to the Logs panel * Added Cloudwatch Logs Insights Target * Added overrides to panels -* ... - -Changes -------- - -* ... - -0.6.1 (TBD) -================== - -* Added new SqlTarget to core to be able to define SQL queries as well - -Changes -------- -* Add SqlTarget 0.6.0 (2021-10-26) =================== From c5335949a2e583990f0d01a1d7473ac4f6680422 Mon Sep 17 00:00:00 2001 From: milkpirate Date: Mon, 29 Nov 2021 11:17:27 +0100 Subject: [PATCH 293/403] Extend SeriesOverride (#434) * add new options and validators * comply lint * readd random * add tests * upd chglg * missing . * add fill * upd chglg Co-authored-by: Paul Schroeder --- CHANGELOG.rst | 1 + grafanalib/core.py | 26 ++++++++++++--- grafanalib/tests/test_core.py | 59 +++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e82752f5..9bf67f0e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ Changelog * Added missing attributes to the Logs panel * Added Cloudwatch Logs Insights Target * Added overrides to panels +* Extend ``SeriesOverride`` options 0.6.0 (2021-10-26) diff --git a/grafanalib/core.py b/grafanalib/core.py index 7d35b02a..7b5c9633 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -3266,11 +3266,27 @@ def to_json_data(self): @attr.s class SeriesOverride(object): - alias = attr.ib() - bars = attr.ib(default=False) - lines = attr.ib(default=True) - yaxis = attr.ib(default=1) + """ + To override properties of e.g. Graphs. + + :param alias: Name of the metric to apply to + :param bars: Whether to show data point bars + :param lines: Whether to keep graph lines + :param yaxis: Whether to move axis of the metric to the right (=2) or not (=1) + :param fill: Fill strength (0...10) + :param color: Whether to change color to + :param fillBelowTo: Alias of the other metric to fill below + """ + alias = attr.ib(validator=instance_of(str)) + bars = attr.ib(default=False, validator=instance_of(bool)) + lines = attr.ib(default=True, validator=instance_of(bool)) + yaxis = attr.ib(default=1, validator=attr.validators.in_([1, 2])) + fill = attr.ib(default=1, validator=attr.validators.in_(range(11))) color = attr.ib(default=None) + fillBelowTo = attr.ib( + default=None, + validator=attr.validators.instance_of((str, type(None))) + ) def to_json_data(self): return { @@ -3278,7 +3294,9 @@ def to_json_data(self): 'bars': self.bars, 'lines': self.lines, 'yaxis': self.yaxis, + 'fill': self.fill, 'color': self.color, + 'fillBelowTo': self.fillBelowTo, } diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index b1ab24d0..89d6c397 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -463,6 +463,65 @@ def test_alert_list(): alert_list.to_json_data() +def test_SeriesOverride_exception_checks(): + with pytest.raises(TypeError): + G.SeriesOverride() + + with pytest.raises(TypeError): + G.SeriesOverride(123) + + with pytest.raises(TypeError): + G.SeriesOverride('alias', bars=123) + + with pytest.raises(TypeError): + G.SeriesOverride('alias', lines=123) + + with pytest.raises(ValueError): + G.SeriesOverride('alias', yaxis=123) + with pytest.raises(ValueError): + G.SeriesOverride('alias', yaxis='abc') + + with pytest.raises(TypeError): + G.SeriesOverride('alias', fillBelowTo=123) + + with pytest.raises(ValueError): + G.SeriesOverride('alias', fill="foo") + with pytest.raises(ValueError): + G.SeriesOverride('alias', fill=123) + with pytest.raises(ValueError): + G.SeriesOverride('alias', fill=-2) + + +def test_SeriesOverride(): + t = G.SeriesOverride('alias').to_json_data() + + assert t['alias'] == 'alias' + assert t['bars'] is False + assert t['lines'] is True + assert t['yaxis'] == 1 + assert t['fill'] == 1 + assert t['color'] is None + assert t['fillBelowTo'] is None + + t = G.SeriesOverride( + 'alias', + bars=True, + lines=False, + yaxis=2, + fill=7, + color='#abc', + fillBelowTo='other_alias' + ).to_json_data() + + assert t['alias'] == 'alias' + assert t['bars'] is True + assert t['lines'] is False + assert t['yaxis'] == 2 + assert t['fill'] == 7 + assert t['color'] == '#abc' + assert t['fillBelowTo'] == 'other_alias' + + def test_alert(): alert = G.Alert( name='dummy name', From a5f7207a4efb4293750b898ccf8e8bc3716bb17a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Nov 2021 12:03:20 +0000 Subject: [PATCH 294/403] Bump sphinx from 4.3.0 to 4.3.1 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.3.0 to 4.3.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.3.0...v4.3.1) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index b65fa733..53bc00c0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.3.0 +sphinx == 4.3.1 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From dbac952f0216bcce440eee9c0fa0b3c8735c5d37 Mon Sep 17 00:00:00 2001 From: milkpirate Date: Wed, 1 Dec 2021 13:26:07 +0100 Subject: [PATCH 295/403] Fix text panel (#436) * fix class * add tests * upd chglg * add warning for grafana <8.0.6 Co-authored-by: Paul Schroeder --- CHANGELOG.rst | 8 +++++++- grafanalib/core.py | 19 +++++++++++-------- grafanalib/tests/test_core.py | 27 +++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9bf67f0e..f507c385 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -11,11 +11,17 @@ Changelog * Added overrides to panels * Extend ``SeriesOverride`` options +Changes +------- + +* Fix Text panel (and add tests) + + **ATTENTION:** This might break panels generated for Grafana <8.0.6 0.6.0 (2021-10-26) =================== -* Added Discrete panel +* Added Discrete panel (https://grafana.com/grafana/plugins/natel-discrete-panel/) * Added support for colors in stat mapping panel with StatValueMappings & StatRangeMappings * Added missing auto interval properties in Template * Added param to RowPanel to collapse the row diff --git a/grafanalib/core.py b/grafanalib/core.py index 7b5c9633..cca26e78 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1871,19 +1871,22 @@ def to_json_data(self): class Text(Panel): """Generates a Text panel.""" - content = attr.ib(default="") + content = attr.ib(default="", validator=instance_of(str)) error = attr.ib(default=False, validator=instance_of(bool)) - mode = attr.ib(default=TEXT_MODE_MARKDOWN) + mode = attr.ib( + default=TEXT_MODE_MARKDOWN, + validator=in_([TEXT_MODE_MARKDOWN, TEXT_MODE_HTML, TEXT_MODE_TEXT]) + ) def to_json_data(self): - return self.panel_json( - { + return self.panel_json({ + 'type': TEXT_TYPE, + 'error': self.error, + 'options': { 'content': self.content, - 'error': self.error, 'mode': self.mode, - 'type': TEXT_TYPE, - } - ) + }, + }) @attr.s diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 89d6c397..9fb3d9bd 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -138,6 +138,33 @@ def test_stat_no_repeat(): assert t.to_json_data()['maxPerRow'] is None +def test_Text_exception_checks(): + with pytest.raises(TypeError): + G.Text(content=123) + + with pytest.raises(TypeError): + G.Text(error=123) + + with pytest.raises(ValueError): + G.Text(mode=123) + + +def test_Text(): + t = G.Text() + + json_data = t.to_json_data() + assert json_data['error'] is False + assert json_data['options']['content'] == "" + assert json_data['options']['mode'] == G.TEXT_MODE_MARKDOWN + + t = G.Text(content='foo', error=True, mode=G.TEXT_MODE_HTML) + + json_data = t.to_json_data() + assert json_data['error'] is True + assert json_data['options']['content'] == "foo" + assert json_data['options']['mode'] == G.TEXT_MODE_HTML + + def test_DiscreteColorMappingItem_exception_checks(): with pytest.raises(TypeError): G.DiscreteColorMappingItem(123) From de31d3135f494588d26eb3641f83a13e27bdfbd2 Mon Sep 17 00:00:00 2001 From: STandon-3 <70574342+STandon-3@users.noreply.github.com> Date: Fri, 3 Dec 2021 07:51:30 +0000 Subject: [PATCH 296/403] Add threshold type option for thresholds (#437) * Extend SeriesOverride (#434) * add new options and validators * comply lint * readd random * add tests * upd chglg * missing . * add fill * upd chglg Co-authored-by: Paul Schroeder * Bump sphinx from 4.3.0 to 4.3.1 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.3.0 to 4.3.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.3.0...v4.3.1) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Adding percentage option for Threshold class * Deep update overrides and switch gauge panel to fieldConfig.defualts * Remove threshold from statetimeline panel Co-authored-by: milkpirate Co-authored-by: Paul Schroeder Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: James Gibson --- CHANGELOG.rst | 4 +++- grafanalib/core.py | 57 +++++++++++++++++++--------------------------- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f507c385..613c2743 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,9 @@ Changelog ========= -0.6.1 (TBD) +* Added percentage type for thresholds + +0.6.1 (2021-11-23) ================== * Added new SqlTarget to core to be able to define SQL queries as well diff --git a/grafanalib/core.py b/grafanalib/core.py index cca26e78..467cc7eb 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1220,6 +1220,8 @@ class Panel(object): :param repeat: Template's name to repeat Graph on :param span: defines the number of spans that will be used for panel :param targets: list of metric requests for chosen datasource + :param thresholds: single stat thresholds + :param thresholdType: type of threshold, absolute or percentage :param timeFrom: time range that Override relative time :param title: of the panel :param transparent: defines if panel should be transparent @@ -1245,6 +1247,8 @@ class Panel(object): minSpan = attr.ib(default=None) repeat = attr.ib(default=attr.Factory(Repeat), validator=instance_of(Repeat)) span = attr.ib(default=None) + thresholds = attr.ib(default=attr.Factory(list)) + thresholdType = attr.ib(default='absolute') timeFrom = attr.ib(default=None) timeShift = attr.ib(default=None) transparent = attr.ib(default=False, validator=instance_of(bool)) @@ -1261,6 +1265,14 @@ def panel_json(self, overrides): 'description': self.description, 'editable': self.editable, 'error': self.error, + 'fieldConfig': { + 'defaults': { + 'thresholds': { + 'mode': self.thresholdType, + 'steps': self.thresholds + }, + }, + }, 'height': self.height, 'gridPos': self.gridPos, 'hideTimeOverride': self.hideTimeOverride, @@ -1278,9 +1290,9 @@ def panel_json(self, overrides): 'timeShift': self.timeShift, 'title': self.title, 'transparent': self.transparent, - 'transformations': self.transformations + 'transformations': self.transformations, } - res.update(overrides) + _deep_update(res, overrides) _deep_update(res, self.extraJson) return res @@ -1383,6 +1395,7 @@ class Graph(Panel): :param stack: Each series is stacked on top of another :param percentage: Available when Stack is selected. Each series is drawn as a percentage of the total of all series :param thresholds: List of GraphThresholds - Only valid when alert not defined + """ alert = attr.ib(default=None) @@ -1559,7 +1572,6 @@ class TimeSeries(Panel): spanNulls = attr.ib(default=False, validator=instance_of(bool)) showPoints = attr.ib(default='auto', validator=instance_of(str)) stacking = attr.ib(default={}, validator=instance_of(dict)) - thresholds = attr.ib(default=attr.Factory(list)) tooltipMode = attr.ib(default='single', validator=instance_of(str)) unit = attr.ib(default='', validator=instance_of(str)) @@ -1595,10 +1607,6 @@ def to_json_data(self): }, }, 'mappings': self.mappings, - 'thresholds': { - 'mode': 'absolute', - 'steps': self.thresholds - }, 'unit': self.unit }, 'overrides': self.overrides @@ -2004,10 +2012,6 @@ def to_json_data(self): 'custom': {}, 'decimals': self.decimals, 'mappings': self.mappings, - 'thresholds': { - 'mode': ABSOLUTE_TYPE, - 'steps': self.thresholds, - }, 'unit': self.format, 'noValue': self.noValue }, @@ -2470,7 +2474,6 @@ class Table(Panel): :param mappings: To assign colors to boolean or string values, use Value mappings :param overrides: To override the base characteristics of certain data :param showHeader: Show the table header - :param thresholds: List of thresholds """ align = attr.ib(default='auto', validator=instance_of(str)) @@ -2483,7 +2486,6 @@ class Table(Panel): overrides = attr.ib(default=attr.Factory(list)) showHeader = attr.ib(default=True, validator=instance_of(bool)) span = attr.ib(default=6) - thresholds = attr.ib(default=attr.Factory(list)) @classmethod def with_styled_columns(cls, columns, styles=None, **kwargs): @@ -2507,10 +2509,6 @@ def to_json_data(self): 'displayMode': self.displayMode, 'filterable': self.filterable }, - "thresholds": { - "mode": "absolute", - "steps": self.thresholds - } }, 'overrides': self.overrides }, @@ -2662,21 +2660,18 @@ class GaugePanel(Panel): def to_json_data(self): return self.panel_json( { - 'options': { - 'fieldOptions': { + 'fieldConfig': { + 'defaults': { 'calcs': [self.calc], - 'defaults': { - 'decimals': self.decimals, - 'max': self.max, - 'min': self.min, - 'title': self.label, - 'unit': self.format, - 'links': self.dataLinks, - }, + 'decimals': self.decimals, + 'max': self.max, + 'min': self.min, + 'title': self.label, + 'unit': self.format, + 'links': self.dataLinks, 'limit': self.limit, 'mappings': self.valueMaps, 'override': {}, - 'thresholds': self.thresholds, 'values': self.allValues, }, 'showThresholdLabels': self.thresholdLabels, @@ -3416,7 +3411,6 @@ class StateTimeline(Panel): :param rowHeight: Controls how much space between rows there are. 1 = no space = 0.5 = 50% space :param showValue: Controls whether values are rendered inside the state regions. Auto will render values if there is sufficient space. :param tooltipMode: Default single - :param thresholds: Thresholds are used to turn the time series into discrete colored state regions """ alignValue = attr.ib(default='left', validator=instance_of(str)) colorMode = attr.ib(default='thresholds', validator=instance_of(str)) @@ -3430,7 +3424,6 @@ class StateTimeline(Panel): rowHeight = attr.ib(default=0.9, validator=instance_of(float)) showValue = attr.ib(default='auto', validator=instance_of(str)) tooltipMode = attr.ib(default='single', validator=instance_of(str)) - thresholds = attr.ib(default=attr.Factory(list)) def to_json_data(self): return self.panel_json( @@ -3444,10 +3437,6 @@ def to_json_data(self): 'color': { 'mode': self.colorMode }, - 'thresholds': { - 'mode': ABSOLUTE_TYPE, - 'steps': self.thresholds, - }, 'mappings': self.mappings }, 'overrides': self.overrides From 4ea6629ffed85b1ccd16ff39b568a799e39a6df5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Dec 2021 12:03:14 +0000 Subject: [PATCH 297/403] Bump actions/setup-python from 2.3.0 to 2.3.1 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.0 to 2.3.1. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.3.0...v2.3.1) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index ab5f9375..f1050aa8 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Set up Python - uses: actions/setup-python@v2.3.0 + uses: actions/setup-python@v2.3.1 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 6e15ffc5..f4b0db7f 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Set up Python 3.7 - uses: actions/setup-python@v2.3.0 + uses: actions/setup-python@v2.3.1 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index fe86f41d..38e427b8 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Set up Python - uses: actions/setup-python@v2.3.0 + uses: actions/setup-python@v2.3.1 with: python-version: ${{ matrix.python }} - name: Run tests From 11df4795326e23d1dc4d96556297375458b8b012 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Dec 2021 12:01:35 +0000 Subject: [PATCH 298/403] Bump lycheeverse/lychee-action from 1.1.1 to 1.2.0 Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.1.1 to 1.2.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.1.1...v1.2.0) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index f1050aa8..d53e3ec8 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.1.1 + uses: lycheeverse/lychee-action@v1.2.0 with: args: --verbose **/*.html - name: Fail if there were link errors From a830ee0c06bf1a0eb5cb4b4f796ee8897e2f3594 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 14 Dec 2021 12:04:43 +0200 Subject: [PATCH 299/403] Added datasource parameter to CloudWatch targets (#442) Co-authored-by: romich --- CHANGELOG.rst | 1 + grafanalib/cloudwatch.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 613c2743..8923fb8d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,7 @@ Changelog ========= * Added percentage type for thresholds +* Added ``datasource`` parameter to CloudWatch targets 0.6.1 (2021-11-23) ================== diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py index dc743747..47d2c452 100644 --- a/grafanalib/cloudwatch.py +++ b/grafanalib/cloudwatch.py @@ -28,6 +28,7 @@ class CloudwatchMetricsTarget(object): :param region: Cloudwatch region :param statistics: Cloudwatch mathematic statistic :param hide: controls if given metric is displayed on visualization + :param datasource: Grafana datasource name """ alias = attr.ib(default="") dimensions = attr.ib(default={}, validator=instance_of(dict)) @@ -41,6 +42,7 @@ class CloudwatchMetricsTarget(object): region = attr.ib(default="default") statistics = attr.ib(default=["Average"], validator=instance_of(list)) hide = attr.ib(default=False, validator=instance_of(bool)) + datasource = attr.ib(default=None) def to_json_data(self): @@ -57,6 +59,7 @@ def to_json_data(self): "region": self.region, "statistics": self.statistics, "hide": self.hide, + "datasource": self.datasource, } @@ -79,6 +82,7 @@ class CloudwatchLogsInsightsTarget(object): :param region: Cloudwatch region :param statsGroups: Cloudwatch statsGroups :param hide: controls if given metric is displayed on visualization + :param datasource: Grafana datasource name """ expression = attr.ib(default="") id = attr.ib(default="") @@ -88,6 +92,7 @@ class CloudwatchLogsInsightsTarget(object): region = attr.ib(default="default") statsGroups = attr.ib(default=[], validator=instance_of(list)) hide = attr.ib(default=False, validator=instance_of(bool)) + datasource = attr.ib(default=None) def to_json_data(self): @@ -101,4 +106,5 @@ def to_json_data(self): "region": self.region, "statsGroups": self.statsGroups, "hide": self.hide, + "datasource": self.datasource, } From 505989ea58ed4523d2e0b8920cd85b2ef9cc1c2a Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Wed, 15 Dec 2021 08:54:26 +0000 Subject: [PATCH 300/403] Added support for auto panels ids to AlertList panel (#444) --- CHANGELOG.rst | 1 + grafanalib/core.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 8923fb8d..b4522ac0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,7 @@ Changelog * Added percentage type for thresholds * Added ``datasource`` parameter to CloudWatch targets +* Added support for auto panels ids to AlertList panel 0.6.1 (2021-11-23) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 467cc7eb..8fc0e2f6 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1948,6 +1948,9 @@ class AlertList(object): title = attr.ib(default="") transparent = attr.ib(default=False, validator=instance_of(bool)) + def _map_panels(self, f): + return f(self) + def to_json_data(self): return { 'dashboardTags': self.dashboardTags, From ff6426488c018e204a0cacbac9504570dc13a34f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Dec 2021 16:17:22 +0000 Subject: [PATCH 301/403] Bump sphinx from 4.3.1 to 4.3.2 (#446) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.3.1 to 4.3.2. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.3.1...v4.3.2) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 53bc00c0..7ad89478 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.3.1 +sphinx == 4.3.2 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From f75ebddc6f5bcf9427c30a893c80d53472d7ea25 Mon Sep 17 00:00:00 2001 From: JamesGibo Date: Fri, 7 Jan 2022 09:13:38 +0000 Subject: [PATCH 302/403] Test Python 3.10 (#447) --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 0b0635de..9d4f500c 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py36, py37, py38, py39 +envlist = py36, py37, py38, py39, py310 [testenv] commands = pytest -o junit_family=xunit2 --junitxml=test-results/junit-{envname}.xml From 68b2cf8bb3a945318fce7bb335a80f9f60b49de3 Mon Sep 17 00:00:00 2001 From: Gaku Yasui Date: Fri, 7 Jan 2022 18:14:30 +0900 Subject: [PATCH 303/403] Add support for fields value in Stat panel (#448) --- CHANGELOG.rst | 1 + grafanalib/core.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b4522ac0..b6676dc2 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ Changelog * Added percentage type for thresholds * Added ``datasource`` parameter to CloudWatch targets * Added support for auto panels ids to AlertList panel +* Added support for fields value in Stat panel 0.6.1 (2021-11-23) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 8fc0e2f6..c9bb973e 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1990,6 +1990,7 @@ class Stat(Panel): :param overrides: To override the base characteristics of certain timeseries data :param reduceCalc: algorithm for reduction to a single value: keys 'mean' 'lastNotNull' 'last' 'first' 'firstNotNull' 'min' 'max' 'sum' 'total' + :param fields: should be included in the panel :param textMode: define Grafana will show name or value: keys: 'auto' 'name' 'none' 'value' 'value_and_name' :param thresholds: single stat thresholds """ @@ -2004,6 +2005,7 @@ class Stat(Panel): orientation = attr.ib(default='auto') overrides = attr.ib(default=attr.Factory(list)) reduceCalc = attr.ib(default='mean', type=str) + fields = attr.ib(default="") textMode = attr.ib(default='auto') thresholds = attr.ib(default="") @@ -2030,7 +2032,7 @@ def to_json_data(self): 'calcs': [ self.reduceCalc ], - 'fields': '', + 'fields': self.fields, 'values': False } }, From 0240500c2f9f18247755478a29c71e6e05be2779 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 13 Jan 2022 14:53:29 +0000 Subject: [PATCH 304/403] added alertName parameter to AlertList panel (#449) Co-authored-by: romich --- CHANGELOG.rst | 1 + grafanalib/core.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b6676dc2..77a52216 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ Changelog * Added ``datasource`` parameter to CloudWatch targets * Added support for auto panels ids to AlertList panel * Added support for fields value in Stat panel +* Added ``alertName`` parameter to AlertList panel 0.6.1 (2021-11-23) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index c9bb973e..480128ac 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1922,6 +1922,7 @@ class AlertList(object): An empty list means all alerts. :param title: The panel title. :param transparent: If true, display the panel without a background. + :param alertName: Show only alerts that contain alertName in their name. """ dashboardTags = attr.ib( @@ -1947,6 +1948,7 @@ class AlertList(object): stateFilter = attr.ib(default=attr.Factory(list)) title = attr.ib(default="") transparent = attr.ib(default=False, validator=instance_of(bool)) + alertName = attr.ib(default="", validator=instance_of(str)) def _map_panels(self, f): return f(self) @@ -1968,6 +1970,9 @@ def to_json_data(self): 'title': self.title, 'transparent': self.transparent, 'type': ALERTLIST_TYPE, + "options": { + "alertName": self.alertName + }, } From c60dad4791533f65dd2e4f5358a6aa9afda1bbc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jan 2022 12:02:25 +0000 Subject: [PATCH 305/403] Bump sphinx from 4.3.2 to 4.4.0 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.3.2 to 4.4.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.3.2...v4.4.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 7ad89478..2c570fc0 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.3.2 +sphinx == 4.4.0 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From 8c73d80d9df007b8da46d618354d035750f28e79 Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Wed, 23 Feb 2022 16:05:29 +0000 Subject: [PATCH 306/403] Update link in changelog to fix CI (#460) --- CHANGELOG.rst | 4 ++-- grafanalib/core.py | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 77a52216..ef1ab928 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -52,7 +52,7 @@ Changes * Added colour overrides to pie chart panel * Added missing attributes from xAxis class -* Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/transformations/types-options/#transformation-types-and-options) +* Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/reference-transformation-functions/) * Added Worldmap panel (https://grafana.com/grafana/plugins/grafana-worldmap-panel/) * Added missing fill gradient to Graph panel * Added missing align to graph panel @@ -124,7 +124,7 @@ Changes * Added Alert Threshold enabled/disabled to Graphs. * Added constants for all Grafana value formats -* Added support for repetitions to Stat Panels (https://grafana.com/docs/grafana/latest/variables/repeat-panels-or-rows/) +* Added support for repetitions to Stat Panels * Added textMode option to Stat Panels * Add Panel object for all panels to inherit from * Add Dashboard list panel (https://grafana.com/docs/grafana/latest/panels/visualizations/dashboard-list-panel/) diff --git a/grafanalib/core.py b/grafanalib/core.py index 480128ac..a2a03c46 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -3186,7 +3186,6 @@ def to_json_data(self): @attr.s class Threshold(object): """Threshold for for panels - (https://grafana.com/docs/grafana/latest/panels/thresholds/) :param color: Color of threshold :param index: Index of color in panel @@ -3226,7 +3225,6 @@ def to_json_data(self): @attr.s class GraphThreshold(object): """Threshold for for Graph panel - (https://grafana.com/docs/grafana/latest/panels/thresholds/) :param colorMode: Color mode of the threshold, value can be `ok`, `warning`, `critical` or `custom`. If `custom` is selcted a lineColor and fillColor should be provided From 793d5fb4eab6c0aa02fa8f2986e9dd2efc84627a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Feb 2022 16:07:47 +0000 Subject: [PATCH 307/403] Bump actions/setup-python from 2.3.1 to 2.3.2 (#455) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.1 to 2.3.2. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.3.1...v2.3.2) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index d53e3ec8..69317f65 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Set up Python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index f4b0db7f..79379710 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Set up Python 3.7 - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 38e427b8..9a028022 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v2.4.0 - name: Set up Python - uses: actions/setup-python@v2.3.1 + uses: actions/setup-python@v2.3.2 with: python-version: ${{ matrix.python }} - name: Run tests From 263dd998bfc671e53787df94d166f41d3f4f3d6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Feb 2022 16:08:08 +0000 Subject: [PATCH 308/403] Bump lycheeverse/lychee-action from 1.2.0 to 1.3.1 (#461) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.2.0 to 1.3.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.2.0...v1.3.1) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 69317f65..90eca941 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.2.0 + uses: lycheeverse/lychee-action@v1.3.1 with: args: --verbose **/*.html - name: Fail if there were link errors From 074d557d2e2a0f2113a4ac5334f7117377af5a85 Mon Sep 17 00:00:00 2001 From: Gaku Yasui Date: Thu, 24 Feb 2022 01:13:27 +0900 Subject: [PATCH 309/403] Add thresholdsStyleMode parameter (#457) --- CHANGELOG.rst | 1 + grafanalib/core.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ef1ab928..f417a250 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ Changelog * Added support for auto panels ids to AlertList panel * Added support for fields value in Stat panel * Added ``alertName`` parameter to AlertList panel +* Added ``thresholdsStyleMode`` parameter to TimeSeries panel 0.6.1 (2021-11-23) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index a2a03c46..84f0135a 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1551,6 +1551,7 @@ class TimeSeries(Panel): :param tooltipMode: When you hover your cursor over the visualization, Grafana can display tooltips single (Default), multi, none :param unit: units + :param thresholdsStyleMode: thresholds style mode off (Default), area, line, line+area """ axisPlacement = attr.ib(default='auto', validator=instance_of(str)) @@ -1574,6 +1575,7 @@ class TimeSeries(Panel): stacking = attr.ib(default={}, validator=instance_of(dict)) tooltipMode = attr.ib(default='single', validator=instance_of(str)) unit = attr.ib(default='', validator=instance_of(str)) + thresholdsStyleMode = attr.ib(default='off', validator=instance_of(str)) def to_json_data(self): return self.panel_json( @@ -1605,6 +1607,9 @@ def to_json_data(self): 'viz': False, 'legend': False }, + 'thresholdsStyle': { + 'mode': self.thresholdsStyleMode + }, }, 'mappings': self.mappings, 'unit': self.unit From d322b0b27026a56a2bb769c0ced809ac37289095 Mon Sep 17 00:00:00 2001 From: milkpirate Date: Wed, 23 Feb 2022 17:15:19 +0100 Subject: [PATCH 310/403] dashes and Z-index (#454) --- CHANGELOG.rst | 1 + grafanalib/core.py | 14 ++++++++++++++ grafanalib/tests/test_core.py | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f417a250..0e737861 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,7 @@ Changelog * Added percentage type for thresholds * Added ``datasource`` parameter to CloudWatch targets * Added support for auto panels ids to AlertList panel +* Added ``SeriesOverride`` options (dashes and Z-index) * Added support for fields value in Stat panel * Added ``alertName`` parameter to AlertList panel * Added ``thresholdsStyleMode`` parameter to TimeSeries panel diff --git a/grafanalib/core.py b/grafanalib/core.py index 84f0135a..c4d05bce 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -3287,12 +3287,22 @@ class SeriesOverride(object): :param fill: Fill strength (0...10) :param color: Whether to change color to :param fillBelowTo: Alias of the other metric to fill below + :param zindex: Move things to front or background (-3...3) + :param dashed: Whether to dash the line + :param dashLength: Length of dashes (1..20) + :param spaceLength: Length of spaces betwee dashed + :param zindex: Move things to front or background """ alias = attr.ib(validator=instance_of(str)) bars = attr.ib(default=False, validator=instance_of(bool)) lines = attr.ib(default=True, validator=instance_of(bool)) yaxis = attr.ib(default=1, validator=attr.validators.in_([1, 2])) fill = attr.ib(default=1, validator=attr.validators.in_(range(11))) + zindex = attr.ib(default=0, validator=attr.validators.in_(range(-3, 4))) + dashes = attr.ib(default=False, validator=instance_of(bool)) + dashLength = attr.ib(default=None, validator=attr.validators.in_([*range(1, 21), None])) + spaceLength = attr.ib(default=None, validator=attr.validators.in_([*range(1, 21), None])) + color = attr.ib(default=None) fillBelowTo = attr.ib( default=None, @@ -3308,6 +3318,10 @@ def to_json_data(self): 'fill': self.fill, 'color': self.color, 'fillBelowTo': self.fillBelowTo, + 'zindex': self.zindex, + 'dashes': self.dashes, + 'dashLength': self.dashLength, + 'spaceLength': self.spaceLength, } diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 9fb3d9bd..048be73c 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -517,6 +517,25 @@ def test_SeriesOverride_exception_checks(): G.SeriesOverride('alias', fill=123) with pytest.raises(ValueError): G.SeriesOverride('alias', fill=-2) + with pytest.raises(ValueError): + G.SeriesOverride('alias', zindex=5) + + with pytest.raises(TypeError): + G.SeriesOverride('alias', dashes="foo") + + with pytest.raises(ValueError): + G.SeriesOverride('alias', dashLength=-2) + with pytest.raises(ValueError): + G.SeriesOverride('alias', dashLength=25) + with pytest.raises(ValueError): + G.SeriesOverride('alias', spaceLength=-2) + with pytest.raises(ValueError): + G.SeriesOverride('alias', spaceLength=25) + + with pytest.raises(ValueError): + G.SeriesOverride('alias', dashLength="foo") + with pytest.raises(ValueError): + G.SeriesOverride('alias', spaceLength="foo") def test_SeriesOverride(): @@ -529,6 +548,10 @@ def test_SeriesOverride(): assert t['fill'] == 1 assert t['color'] is None assert t['fillBelowTo'] is None + assert t['dashes'] is False + assert t['dashLength'] is None + assert t['spaceLength'] is None + assert t['zindex'] == 0 t = G.SeriesOverride( 'alias', @@ -537,7 +560,11 @@ def test_SeriesOverride(): yaxis=2, fill=7, color='#abc', - fillBelowTo='other_alias' + fillBelowTo='other_alias', + dashes=True, + dashLength=12, + spaceLength=17, + zindex=-2, ).to_json_data() assert t['alias'] == 'alias' @@ -547,6 +574,10 @@ def test_SeriesOverride(): assert t['fill'] == 7 assert t['color'] == '#abc' assert t['fillBelowTo'] == 'other_alias' + assert t['dashes'] is True + assert t['dashLength'] == 12 + assert t['spaceLength'] == 17 + assert t['zindex'] == -2 def test_alert(): From e7f0a9de5f88df6e36304cb447f540f77d44c7e1 Mon Sep 17 00:00:00 2001 From: Andrew Z Allen Date: Wed, 23 Feb 2022 09:23:03 -0700 Subject: [PATCH 311/403] Add `to_json_data` for `Repeat` class (#453) --- grafanalib/core.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index c4d05bce..ce7b5163 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -380,6 +380,13 @@ class Repeat(object): variable = attr.ib(default=None) maxPerRow = attr.ib(default=None, validator=is_valid_max_per_row) + def to_json_data(self): + return { + 'direction': self.direction, + 'variable': self.variable, + 'maxPerRow': self.maxPerRow, + } + def is_valid_target(instance, attribute, value): """ From 30eb538a3feb53b88b2e97c6d91072010663bb3a Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Thu, 24 Feb 2022 15:53:25 +0000 Subject: [PATCH 312/403] Add option to overwrite dashboards in upload script (#464) --- .../tests/examples/example.upload-dashboard.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/grafanalib/tests/examples/example.upload-dashboard.py b/grafanalib/tests/examples/example.upload-dashboard.py index 73f1bc0e..588379b8 100644 --- a/grafanalib/tests/examples/example.upload-dashboard.py +++ b/grafanalib/tests/examples/example.upload-dashboard.py @@ -5,7 +5,7 @@ from os import getenv -def get_dashboard_json(dashboard): +def get_dashboard_json(dashboard, overwrite=False, message="Updated by grafanlib"): ''' get_dashboard_json generates JSON from grafanalib Dashboard object @@ -13,10 +13,15 @@ def get_dashboard_json(dashboard): ''' # grafanalib generates json which need to pack to "dashboard" root element - return json.dumps({"dashboard": dashboard.to_json_data()}, sort_keys=True, indent=2, cls=DashboardEncoder) + return json.dumps( + { + "dashboard": dashboard.to_json_data(), + "overwrite": overwrite, + "message": message + }, sort_keys=True, indent=2, cls=DashboardEncoder) -def upload_to_grafana(json, server, api_key): +def upload_to_grafana(json, server, api_key, verify=True): ''' upload_to_grafana tries to upload dashboard to grafana and prints response @@ -26,7 +31,7 @@ def upload_to_grafana(json, server, api_key): ''' headers = {'Authorization': f"Bearer {api_key}", 'Content-Type': 'application/json'} - r = requests.post(f"https://{server}/api/dashboards/db", data=json, headers=headers) + r = requests.post(f"https://{server}/api/dashboards/db", data=json, headers=headers, verify=verify) # TODO: add error handling print(f"{r.status_code} - {r.content}") @@ -34,6 +39,6 @@ def upload_to_grafana(json, server, api_key): grafana_api_key = getenv("GRAFANA_API_KEY") grafana_server = getenv("GRAFANA_SERVER") -my_dashboard = Dashboard(title="My awesome dashboard") -my_dashboard_json = get_dashboard_json(my_dashboard) +my_dashboard = Dashboard(title="My awesome dashboard", uid='abifsd') +my_dashboard_json = get_dashboard_json(my_dashboard, overwrite=True) upload_to_grafana(my_dashboard_json, grafana_server, grafana_api_key) From 4a33bbb3b0a9dc069b7b343f4b83074969f92dc7 Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Thu, 24 Feb 2022 15:53:53 +0000 Subject: [PATCH 313/403] Add support for Histogram panel (#463) --- CHANGELOG.rst | 1 + grafanalib/core.py | 62 +++++++++++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 17 ++++++++++ 3 files changed, 80 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0e737861..fc802a0e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -9,6 +9,7 @@ Changelog * Added support for fields value in Stat panel * Added ``alertName`` parameter to AlertList panel * Added ``thresholdsStyleMode`` parameter to TimeSeries panel +* Added Histogram panel support 0.6.1 (2021-11-23) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index ce7b5163..a7418ed4 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -96,6 +96,7 @@ def to_json_data(self): TIMESERIES_TYPE = 'timeseries' WORLD_MAP_TYPE = 'grafana-worldmap-panel' NEWS_TYPE = 'news' +HISTOGRAM_TYPE = 'histogram' DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -3493,6 +3494,67 @@ def to_json_data(self): ) +@attr.s +class Histogram(Panel): + """Generates Histogram panel json structure + Grafana docs on Histogram panel: https://grafana.com/docs/grafana/latest/visualizations/histogram/# + + :param bucketOffset: Bucket offset for none-zero-based buckets + :param bucketSize: Bucket size, default Auto + :param colorMode: Default thresholds + :param combine: Combine all series into a single histogram + :param fillOpacity: Controls the opacity of state regions, default 0.9 + :param legendDisplayMode: refine how the legend appears, list, table or hidden + :param legendPlacement: bottom or top + :param lineWidth: Controls line width of state regions + :param mappings: To assign colors to boolean or string values, use Value mappings + :param overrides: To override the base characteristics of certain data + """ + bucketOffset = attr.ib(default=0, validator=instance_of(int)) + bucketSize = attr.ib(default=0, validator=instance_of(int)) + colorMode = attr.ib(default='thresholds', validator=instance_of(str)) + combine = attr.ib(default=False, validator=instance_of(bool)) + fillOpacity = attr.ib(default=80, validator=instance_of(int)) + legendDisplayMode = attr.ib(default='list', validator=instance_of(str)) + legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) + lineWidth = attr.ib(default=0, validator=instance_of(int)) + mappings = attr.ib(default=attr.Factory(list)) + overrides = attr.ib(default=attr.Factory(list)) + + def to_json_data(self): + histogram = self.panel_json( + { + 'fieldConfig': { + 'defaults': { + 'custom': { + 'lineWidth': self.lineWidth, + 'fillOpacity': self.fillOpacity + }, + 'color': { + 'mode': self.colorMode + }, + 'mappings': self.mappings + }, + 'overrides': self.overrides + }, + 'options': { + 'legend': { + 'displayMode': self.legendDisplayMode, + 'placement': self.legendPlacement + }, + "bucketOffset": self.bucketOffset, + "combine": self.combine, + }, + 'type': HISTOGRAM_TYPE, + } + ) + + if self.bucketSize > 0: + histogram['options']['bucketSize'] = self.bucketSize + + return histogram + + @attr.s class News(Panel): """Generates News panel json structure diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 048be73c..e7a2c258 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -681,6 +681,23 @@ def test_pieChartv2(): assert data["title"] == title +def test_histogram(): + data_source = "dummy data source" + targets = ["dummy_prom_query"] + title = "dummy title" + panel = G.Histogram(data_source, targets, title) + data = panel.to_json_data() + assert data["targets"] == targets + assert data["datasource"] == data_source + assert data["title"] == title + assert 'bucketSize' not in data['options'] + + bucketSize = 5 + panel = G.Histogram(data_source, targets, title, bucketSize=bucketSize) + data = panel.to_json_data() + assert data['options']['bucketSize'] == bucketSize + + def test_sql_target(): t = G.Table( dataSource="some data source", From 23a5dc223319e6ca43d99d661a25e45e1c26096b Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Thu, 24 Feb 2022 15:54:23 +0000 Subject: [PATCH 314/403] Update example dashboard to latest features and work on vanila grafana install (#462) --- docs/getting-started.rst | 7 +- grafanalib/core.py | 11 ++ .../tests/examples/example.dashboard.py | 104 ++++++------------ 3 files changed, 47 insertions(+), 75 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 03c94f50..f9f7298a 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -19,9 +19,8 @@ or submit a PR to the GitHub `repository Date: Thu, 24 Feb 2022 15:59:17 +0000 Subject: [PATCH 315/403] Prep for 0.6.2 release (#465) --- CHANGELOG.rst | 4 ++++ setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fc802a0e..855baa18 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,9 @@ Changelog ========= +0.6.2 (2022-02-24) +================== + * Added percentage type for thresholds * Added ``datasource`` parameter to CloudWatch targets * Added support for auto panels ids to AlertList panel @@ -10,6 +13,7 @@ Changelog * Added ``alertName`` parameter to AlertList panel * Added ``thresholdsStyleMode`` parameter to TimeSeries panel * Added Histogram panel support +* Dashboard upload script updated to support overwriting dashboards 0.6.1 (2021-11-23) ================== diff --git a/setup.py b/setup.py index 5b850f73..6c7bbb67 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.6.1', + version='0.6.2', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From bcd49a9692a250179dc981f941380274dd23dfa5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:38:02 +0100 Subject: [PATCH 316/403] Bump sphinx from 4.4.0 to 4.5.0 (#479) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.4.0 to 4.5.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.4.0...v4.5.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 2c570fc0..5e82e6eb 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.4.0 +sphinx == 4.5.0 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From 3c3a7d4c7e690de7dacef1692f34ab201bb88813 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:38:21 +0100 Subject: [PATCH 317/403] Bump actions/checkout from 2.4.0 to 3 (#467) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 90eca941..81c421cc 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -7,7 +7,7 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2.3.2 with: diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 79379710..3676bfe3 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -7,7 +7,7 @@ jobs: name: Build and publish Python 🐍 distributions 📦 to PyPI runs-on: ubuntu-18.04 steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Set up Python 3.7 uses: actions/setup-python@v2.3.2 with: diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 9a028022..5145c851 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: matrix: python: ['3.6', '3.7', '3.8', '3.9', '3.10'] steps: - - uses: actions/checkout@v2.4.0 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2.3.2 with: From 2ff3ab9bcd6247ffe47683ab1185b59a7c283f11 Mon Sep 17 00:00:00 2001 From: Joachim Lusiardi Date: Tue, 5 Apr 2022 16:49:06 +0200 Subject: [PATCH 318/403] Add support to set legend values on TimeSeries (#471) Co-authored-by: Joachim Lusiardi --- CHANGELOG.rst | 2 ++ grafanalib/core.py | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 855baa18..dea1e574 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,8 @@ Changelog ========= +* Added ``legendCalcs`` parameter to TimeSeries Panel + 0.6.2 (2022-02-24) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 11942c90..c0a5716d 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1554,6 +1554,10 @@ class TimeSeries(Panel): :param legendDisplayMode: refine how the legend appears in your visualization list (Default), table, hidden :param legendPlacement: bottom (Default), right + :param legendCalcs: which calculations should be displayed in the legend. Defaults to an empty list. + Possible values are: allIsNull, allIsZero, changeCount, count, delta, diff, diffperc, + distinctCount, firstNotNull, max, mean, min, logmin, range, step, total. For more information see + https://grafana.com/docs/grafana/next/panels/reference-calculation-types/ :param lineInterpolation: line interpolation linear (Default), smooth, stepBefore, stepAfter :param lineWidth: line width, default 1 @@ -1582,6 +1586,34 @@ class TimeSeries(Panel): gradientMode = attr.ib(default='none', validator=instance_of(str)) legendDisplayMode = attr.ib(default='list', validator=instance_of(str)) legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) + legendCalcs = attr.ib( + default=[], + validator=attr.validators.deep_iterable( + member_validator=in_([ + 'lastNotNull', + 'min', + 'mean', + 'max', + 'last', + 'firstNotNull', + 'first', + 'sum', + 'count', + 'range', + 'delta', + 'step', + 'diff', + 'logmin', + 'allIsZero', + 'allIsNull', + 'changeCount', + 'distinctCount', + 'diffperc', + 'allValues' + ]), + iterable_validator=instance_of(list), + ), + ) lineInterpolation = attr.ib(default='linear', validator=instance_of(str)) lineWidth = attr.ib(default=1, validator=instance_of(int)) mappings = attr.ib(default=attr.Factory(list)) @@ -1639,7 +1671,7 @@ def to_json_data(self): 'legend': { 'displayMode': self.legendDisplayMode, 'placement': self.legendPlacement, - 'calcs': [] + 'calcs': self.legendCalcs }, 'tooltip': { 'mode': self.tooltipMode From 8634e1d836bbd5e793c802bb85c4478fa21fa372 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:49:47 +0100 Subject: [PATCH 319/403] Bump actions/setup-python from 2.3.2 to 3.1.0 (#481) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.2 to 3.1.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.3.2...v3.1.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 81c421cc..0aeb6031 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.1.0 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 3676bfe3..bccc4fad 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.7 - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.1.0 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 5145c851..0b704f85 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v3.1.0 with: python-version: ${{ matrix.python }} - name: Run tests From 55cc14af1fc67f4efcf04572c2ff6063abd7569c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:51:19 +0100 Subject: [PATCH 320/403] Bump lycheeverse/lychee-action from 1.3.1 to 1.4.1 (#478) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.3.1 to 1.4.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.3.1...v1.4.1) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 0aeb6031..f27f5c7a 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.3.1 + uses: lycheeverse/lychee-action@v1.4.1 with: args: --verbose **/*.html - name: Fail if there were link errors From 4c04c126061edbde2bc6b76d1014aeaa3e55c6ae Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Tue, 5 Apr 2022 16:19:10 +0100 Subject: [PATCH 321/403] Fix broken zabbix url (#482) --- grafanalib/zabbix.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/grafanalib/zabbix.py b/grafanalib/zabbix.py index 085532ea..4aea0494 100644 --- a/grafanalib/zabbix.py +++ b/grafanalib/zabbix.py @@ -103,7 +103,7 @@ class ZabbixTarget(object): to visualize monitoring data from Zabbix and create dashboards for analyzing metrics and realtime monitoring. - Grafana docs on using Zabbix pluging: http://docs.grafana-zabbix.org/ + Grafana docs on using Zabbix plugin: https://alexanderzobnin.github.io/grafana-zabbix/ :param application: zabbix application name :param expr: zabbix arbitary query @@ -172,7 +172,7 @@ class ZabbixDeltaFunction(object): """ZabbixDeltaFunction Convert absolute values to delta, for example, bits to bits/sec - http://docs.grafana-zabbix.org/reference/functions/#delta + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#delta """ added = attr.ib(default=False, validator=instance_of(bool)) @@ -197,7 +197,7 @@ class ZabbixGroupByFunction(object): Takes each timeseries and consolidate its points falled in given interval into one point using function, which can be one of: avg, min, max, median. - http://docs.grafana-zabbix.org/reference/functions/#groupBy + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions//#groupBy """ _options = ('avg', 'min', 'max', 'median') @@ -238,7 +238,7 @@ class ZabbixScaleFunction(object): """ZabbixScaleFunction Takes timeseries and multiplies each point by the given factor. - http://docs.grafana-zabbix.org/reference/functions/#scale + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions//#scale """ _default_factor = 100 @@ -272,7 +272,7 @@ class ZabbixAggregateByFunction(object): Takes all timeseries and consolidate all its points falled in given interval into one point using function, which can be one of: avg, min, max, median. - http://docs.grafana-zabbix.org/reference/functions/#aggregateBy + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#aggregateBy """ _options = ('avg', 'min', 'max', 'median') @@ -313,7 +313,7 @@ class ZabbixAverageFunction(object): """ZabbixAverageFunction Deprecated, use aggregateBy(interval, avg) instead. - http://docs.grafana-zabbix.org/reference/functions/#average + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#average """ _default_interval = '1m' @@ -347,7 +347,7 @@ class ZabbixMaxFunction(object): """ZabbixMaxFunction Deprecated, use aggregateBy(interval, max) instead. - http://docs.grafana-zabbix.org/reference/functions/#max + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#max """ _default_interval = '1m' @@ -381,7 +381,7 @@ class ZabbixMedianFunction(object): """ZabbixMedianFunction Deprecated, use aggregateBy(interval, median) instead. - http://docs.grafana-zabbix.org/reference/functions/#median + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#median """ _default_interval = '1m' @@ -415,7 +415,7 @@ class ZabbixMinFunction(object): """ZabbixMinFunction Deprecated, use aggregateBy(interval, min) instead. - http://docs.grafana-zabbix.org/reference/functions/#min + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#min """ _default_interval = '1m' @@ -452,7 +452,7 @@ class ZabbixSumSeriesFunction(object): This method required interpolation of each timeseries so it may cause high CPU load. Try to combine it with groupBy() function to reduce load. - http://docs.grafana-zabbix.org/reference/functions/#sumSeries + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#sumSeries """ added = attr.ib(default=False) @@ -549,7 +549,7 @@ class ZabbixTrendValueFunction(object): Specifying type of trend value returned by Zabbix when trends are used (avg, min or max). - http://docs.grafana-zabbix.org/reference/functions/#trendValue + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#trendValue """ _options = ('avg', 'min', 'max') @@ -587,7 +587,7 @@ class ZabbixTimeShiftFunction(object): If no sign is given, a minus sign ( - ) is implied which will shift the metric back in time. If a plus sign ( + ) is given, the metric will be shifted forward in time. - http://docs.grafana-zabbix.org/reference/functions/#timeShift + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#timeShift """ _options = ('24h', '7d', '1M', '+24h', '-24h') @@ -622,7 +622,7 @@ class ZabbixSetAliasFunction(object): """ZabbixSetAliasFunction Returns given alias instead of the metric name. - http://docs.grafana-zabbix.org/reference/functions/#setAlias + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#setAlias """ alias = attr.ib(validator=instance_of(str)) added = attr.ib(default=False, validator=instance_of(bool)) @@ -649,7 +649,7 @@ class ZabbixSetAliasByRegexFunction(object): """ZabbixSetAliasByRegexFunction Returns part of the metric name matched by regex. - http://docs.grafana-zabbix.org/reference/functions/#setAliasByRegex + https://alexanderzobnin.github.io/grafana-zabbix/reference/functions/#setAliasByRegex """ regexp = attr.ib(validator=instance_of(str)) From 3289abb070e35ef60d79408ba6bc41c27ca26f74 Mon Sep 17 00:00:00 2001 From: Cam Date: Wed, 6 Apr 2022 03:15:40 -0400 Subject: [PATCH 322/403] Add Azure Monitor Targets (#480) * Added Azure Monitor Target * Updated Changelog * Added alias property Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- CHANGELOG.rst | 6 ++ grafanalib/azuremonitor.py | 120 ++++++++++++++++++++++++++ grafanalib/tests/test_azuremonitor.py | 79 +++++++++++++++++ 3 files changed, 205 insertions(+) create mode 100644 grafanalib/azuremonitor.py create mode 100644 grafanalib/tests/test_azuremonitor.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dea1e574..6a13e873 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,8 +2,14 @@ Changelog ========= + +0.6.3 (2022-03-30) +================== + +* Added Azure Monitor Target * Added ``legendCalcs`` parameter to TimeSeries Panel + 0.6.2 (2022-02-24) ================== diff --git a/grafanalib/azuremonitor.py b/grafanalib/azuremonitor.py new file mode 100644 index 00000000..a9afc643 --- /dev/null +++ b/grafanalib/azuremonitor.py @@ -0,0 +1,120 @@ +"""Helpers to create Azure Monitor specific Grafana queries.""" + +import attr +from attr.validators import instance_of + + +@attr.s +class AzureMonitorMetricsTarget(object): + """ + Generates Azure Monitor Metrics target JSON structure. + + Grafana docs on using Azure Monitor: + https://grafana.com/docs/grafana/latest/datasources/azuremonitor/#querying-azure-monitor-metrics + + :param aggregation: Metrics Aggregation (Total, None, Minimum, Maximum, Average, Count) + :param dimensionFilters: Dimension Filters + :param metricsDefinition: Metrics Definition https://docs.microsoft.com/en-us/azure/azure-monitor/essentials/metrics-supported + :param metricNamespace: Metrics Namespace + :param resourceGroup: Resource Group the resource resides in + :param timeGrain: Time Granularity + :param queryType: Type of Query (Azure Monitor in this case) + :param subscription: Azure Subscription ID to scope to + :param refId: Reference ID for the target + """ + + aggregation = attr.ib(default="Total") + dimensionFilters = attr.ib(default=[], validator=instance_of(list)) + metricDefinition = attr.ib(default="") + metricName = attr.ib(default="") + metricNamespace = attr.ib(default="") + resourceGroup = attr.ib(default="") + resourceName = attr.ib(default="") + timeGrain = attr.ib(default="auto") + queryType = attr.ib(default="Azure Monitor") + subscription = attr.ib(default="") + refId = attr.ib(default="") + alias = attr.ib(default="") + + def to_json_data(self): + return { + "azureMonitor": { + "aggregation": self.aggregation, + "alias": self.alias, + "dimensionFilters": self.dimensionFilters, + "metricDefinition": self.metricDefinition, + "metricName": self.metricName, + "metricNamespace": self.metricNamespace, + "resourceGroup": self.resourceGroup, + "resourceName": self.resourceName, + "timeGrain": self.timeGrain, + }, + "queryType": self.queryType, + "refId": self.refId, + "subscription": self.subscription, + } + + +@attr.s +class AzureLogsTarget(object): + """ + Generates Azure Monitor Logs target JSON structure. + + Grafana docs on using Azure Logs: + https://grafana.com/docs/grafana/latest/datasources/azuremonitor/#querying-azure-monitor-logs + + :param query: Query to execute + :param resource: Identification string for resource e.g. /subscriptions/1234-abcd/resourceGroups/myResourceGroup/providers/Microsoft.DataFactory/factories/myDataFactory + :param resultFormat: Output Format of the logs + :param queryType: Type of Query (Azure Log Analytics in this case) + :param subscription: Azure Subscription ID to scope to + :param refId: Reference ID for the target + """ + + query = attr.ib(default="") + resource = attr.ib(default="") + resultFormat = attr.ib(default="table") + queryType = attr.ib(default="Azure Log Analytics") + subscription = attr.ib(default="") + refId = attr.ib(default="") + + def to_json_data(self): + return { + "azureLogAnalytics": { + "query": self.query, + "resource": self.resource, + "resultFormat": self.resultFormat, + }, + "queryType": self.queryType, + "refId": self.refId, + "subscription": self.subscription, + } + + +@attr.s +class AzureResourceGraphTarget(object): + """ + Generates Azure Resource Graph target JSON structure. + + Grafana docs on using Azure Resource Graph: + https://grafana.com/docs/grafana/latest/datasources/azuremonitor/#querying-azure-resource-graph + + :param query: Query to execute + :param queryType: Type of Query (Azure Resource Graph in this case) + :param subscription: Azure Subscription ID to scope to + :param refId: Reference ID for the target + """ + + query = attr.ib(default="") + resource = attr.ib(default="") + queryType = attr.ib(default="Azure Resource Graph") + subscription = attr.ib(default="") + refId = attr.ib(default="") + + def to_json_data(self): + return { + "azureResourceGraph": {"query": self.query}, + "queryType": self.queryType, + "refId": self.refId, + "subscription": self.subscription, + } diff --git a/grafanalib/tests/test_azuremonitor.py b/grafanalib/tests/test_azuremonitor.py new file mode 100644 index 00000000..42dfec21 --- /dev/null +++ b/grafanalib/tests/test_azuremonitor.py @@ -0,0 +1,79 @@ +"""Tests for Azure Monitor Datasource""" + +import grafanalib.core as G +import grafanalib.azuremonitor as A +from grafanalib import _gen +from io import StringIO + + +def test_serialization_azure_metrics_target(): + """Serializing a graph doesn't explode.""" + graph = G.TimeSeries( + title="Test Azure Monitor", + dataSource="default", + targets=[ + A.AzureMonitorMetricsTarget( + aggregation="Total", + metricDefinition="Microsoft.Web/sites", + metricName="Requests", + metricNamespace="Microsoft.Web/sites", + resourceGroup="test-grafana", + resourceName="test-grafana", + subscription="3a680d1a-9310-4667-9e6a-9fcd2ecddd86", + refId="Requests", + ), + ], + ) + stream = StringIO() + _gen.write_dashboard(graph, stream) + assert stream.getvalue() != "" + + +def test_serialization_azure_logs_target(): + """Serializing a graph doesn't explode.""" + + logs_query = """AzureMetrics +| where TimeGenerated > ago(30d) +| extend tail_latency = Maximum / Average +| where MetricName == "Http5xx" or (MetricName == "HttpResponseTime" and Average >= 3) or (MetricName == "HttpResponseTime" and tail_latency >= 10 and Average >= 0.5) +| summarize dcount(TimeGenerated) by Resource +| order by dcount_TimeGenerated""" + + graph = G.GaugePanel( + title="Test Logs", + dataSource="default", + targets=[ + A.AzureLogsTarget( + query=logs_query, + resource="/subscriptions/3a680d1a-9310-4667-9e6a-9fcd2ecddd86", + subscription="3a680d1a-9310-4667-9e6a-9fcd2ecddd86", + refId="Bad Minutes", + ), + ], + ) + stream = StringIO() + _gen.write_dashboard(graph, stream) + assert stream.getvalue() != "" + + +def test_serialization_azure_graph_target(): + """Serializing a graph doesn't explode.""" + + graph_query = """Resources +| project name, type, location +| order by name asc""" + + graph = G.GaugePanel( + title="Test Logs", + dataSource="default", + targets=[ + A.AzureLogsTarget( + query=graph_query, + subscription="3a680d1a-9310-4667-9e6a-9fcd2ecddd86", + refId="Resources", + ), + ], + ) + stream = StringIO() + _gen.write_dashboard(graph, stream) + assert stream.getvalue() != "" From 9fb979548772b49f16f00b9a311e1a568ae76a49 Mon Sep 17 00:00:00 2001 From: "Peter Darvasi (EPAM)" <70262315+pdarvasi-epam@users.noreply.github.com> Date: Wed, 6 Apr 2022 09:18:55 +0200 Subject: [PATCH 323/403] added hide field for ElasticSearchTarget and added ExpressionTarget class (#483) * added hide field for ElasticSearchTarget and added ExpressionTarget support * Bump sphinx from 4.4.0 to 4.5.0 (#479) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.4.0 to 4.5.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/4.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.4.0...v4.5.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump actions/checkout from 2.4.0 to 3 (#467) Bumps [actions/checkout](https://github.com/actions/checkout) from 2.4.0 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2.4.0...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add support to set legend values on TimeSeries (#471) Co-authored-by: Joachim Lusiardi * Bump actions/setup-python from 2.3.2 to 3.1.0 (#481) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2.3.2 to 3.1.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2.3.2...v3.1.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Bump lycheeverse/lychee-action from 1.3.1 to 1.4.1 (#478) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.3.1 to 1.4.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.3.1...v1.4.1) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Fix broken zabbix url (#482) * adding tests for Elastic ExpressionTarget Co-authored-by: Peter Darvasi Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Joachim Lusiardi Co-authored-by: Joachim Lusiardi Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6a13e873..f817e225 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,8 @@ Changelog * Added Azure Monitor Target * Added ``legendCalcs`` parameter to TimeSeries Panel +* Added ``hide`` parameter to ElasticsearchTarget +* Added ExpressionTarget support for ElasticSearch data sources 0.6.2 (2022-02-24) From b2b14a7bc824d89194d7b07c6aa9f8d24e1bcf86 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 13:52:12 +0100 Subject: [PATCH 324/403] Bump actions/setup-python from 3.1.0 to 3.1.1 (#486) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v3.1.0...v3.1.1) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index f27f5c7a..4bc73a06 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3.1.0 + uses: actions/setup-python@v3.1.1 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index bccc4fad..48f48edc 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.7 - uses: actions/setup-python@v3.1.0 + uses: actions/setup-python@v3.1.1 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 0b704f85..318c7703 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3.1.0 + uses: actions/setup-python@v3.1.1 with: python-version: ${{ matrix.python }} - name: Run tests From 49108269173e399cb4dde2812a1007aacca9e2ae Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Mon, 11 Apr 2022 14:22:18 +0100 Subject: [PATCH 325/403] Prep 0.6.3 release (#487) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6c7bbb67..45a9fbb5 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/single_source_version.html - version='0.6.2', + version='0.6.3', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From a9c559baf9f4dabb54bf41543939a2b6abd3f1fb Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Mon, 11 Apr 2022 14:27:38 +0100 Subject: [PATCH 326/403] Add placeholder to changelog for next release (#488) --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f817e225..60deab88 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,11 @@ Changelog ========= +x.x.x (TBC) +=========== + +* Added ... + 0.6.3 (2022-03-30) ================== From 4e949625045d16bf745770625d0da381ef2cd4bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Apr 2022 17:18:12 +0100 Subject: [PATCH 327/403] Bump actions/setup-python from 3.1.1 to 3.1.2 (#489) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3.1.1 to 3.1.2. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v3.1.1...v3.1.2) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 4bc73a06..eacf4569 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3.1.1 + uses: actions/setup-python@v3.1.2 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 48f48edc..28efddd6 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.7 - uses: actions/setup-python@v3.1.1 + uses: actions/setup-python@v3.1.2 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 318c7703..6d36d185 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3.1.1 + uses: actions/setup-python@v3.1.2 with: python-version: ${{ matrix.python }} - name: Run tests From d4922c4e142c4b114f409bdc7406f6f6ce4f4196 Mon Sep 17 00:00:00 2001 From: sbng Date: Mon, 16 May 2022 15:23:02 +0800 Subject: [PATCH 328/403] add 'datasource' parameter for InfluxdbTarget (#491) * add 'datasource' paraemter for InfluxdbTarget Under each InfluxdbTarget, a different data source can be specified. This allow the merging of trends from different influxb into the same Panel. This is done by adding the 'datasource' parameter to the InfluxdbTarget object * Update Changelog as per request --- CHANGELOG.rst | 1 + grafanalib/influxdb.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 60deab88..2ca2beef 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ x.x.x (TBC) =========== * Added ... +* Added datasource parameter to Influxdb targets 0.6.3 (2022-03-30) diff --git a/grafanalib/influxdb.py b/grafanalib/influxdb.py index 51638d28..b84384be 100644 --- a/grafanalib/influxdb.py +++ b/grafanalib/influxdb.py @@ -17,6 +17,7 @@ class InfluxDBTarget(object): :param alias: legend alias :param format: Bucket aggregators + :param datasource: Influxdb name (for multiple datasource with same panel) :param measurement: Metric Aggregators :param query: query :param rawQuery: target reference id @@ -25,6 +26,7 @@ class InfluxDBTarget(object): alias = attr.ib(default="") format = attr.ib(default=TIME_SERIES_TARGET_FORMAT) + datasource = attr.ib(default="") measurement = attr.ib(default="") query = attr.ib(default="") rawQuery = attr.ib(default=True) @@ -35,6 +37,7 @@ def to_json_data(self): 'query': self.query, 'resultFormat': self.format, 'alias': self.alias, + 'datasource': self.datasource, 'measurement': self.measurement, 'rawQuery': self.rawQuery, 'refId': self.refId From 755cbfac3dbc251825a03ff1fbb36c12de289e36 Mon Sep 17 00:00:00 2001 From: mharbison72 <57785103+mharbison72@users.noreply.github.com> Date: Mon, 23 May 2022 05:54:52 -0400 Subject: [PATCH 329/403] Add missing format units (#494) * Add boolean format units These were added in Grafana 8.0. They appear at the end of `categories.ts`, so are added to the end here instead of alphabetized. * Add missing Energy, Mass, and Length units Current as of grafana eeaa160ae833. * Add missing Miscellaneous units Current as of grafana eeaa160ae833. * Add missing Date & Time units Current as of grafana eeaa160ae833. * Add missing Data and Data Rate units Current as of grafana eeaa160ae833. * Fix typos in unit names Client updates would be trivial, so I don't think it's worth keeping an alias to the old names. --- CHANGELOG.rst | 4 +++ grafanalib/formatunits.py | 55 ++++++++++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2ca2beef..a16b3579 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,10 @@ x.x.x (TBC) * Added ... * Added datasource parameter to Influxdb targets +* Added missing units for Boolean, Data, Data Rate, Date & Time, Energy, Length, + Mass, and Misc +* Fix typo in unit constant ``GIGA_WATT`` (was ``GAGA_WATT``) +* Fix typo in unit constant ``NORMAL_CUBIC_METER`` (was ``NORMAIL_CUBIC_METER``) 0.6.3 (2022-03-30) diff --git a/grafanalib/formatunits.py b/grafanalib/formatunits.py index 51022113..54112427 100644 --- a/grafanalib/formatunits.py +++ b/grafanalib/formatunits.py @@ -10,9 +10,18 @@ NO_FORMAT = 'none' NONE_FORMAT = 'none' +NUMBER_FORMAT = 'none' +STRING_FORMAT = 'string' PERCENT_UNIT = 'percentunit' PERCENT_FORMAT = 'percent' SHORT = 'short' +HUMIDITY = 'humidity' # %H +DECIBEL = 'dB' +HEXADECIMAL_OX = 'hex0x' # 0x +HEXADECIMAL = 'hex' +SCI_NOTATION = 'sci' +LOCAL_FORMAT = 'locale' +PIXELS = 'pixel' # Acceleration METERS_SEC_2 = 'accMS2' # m/sec² FEET_SEC_2 = 'accFS2' # f/sec² @@ -72,32 +81,65 @@ SOUTH_KOREAN_WON = 'currencyKRW' # ₩ INDONESIAN_RUPIAH = 'currencyIDR' # Rp PHILIPPINE_PESO = 'currencyPHP' # PHP -# Data (metric) +# Data +BYTES_IEC = 'bytes' BYTES = 'decbytes' # B +BITS_IEC = 'bits' +BITS = 'decbits' +KIBI_BYTES = 'kbytes' # KiB KILO_BYTES = 'deckbytes' # kB +MEBI_BYTES = 'mbytes' # MiB MEGA_BYTES = 'decmbytes' # MB +GIBI_BYTES = 'gbytes' # GiB GIGA_BYTES = 'decgbytes' # GB +TEBI_BYTES = 'tbytes' # TiB TERA_BYTES = 'dectbytes' # TB +PEBI_BYTES = 'pbytes' # PiB PETA_BYTES = 'decpbytes' # PB # Data Rate PACKETS_SEC = 'pps' # p/s + +BYTES_SEC_IEC = 'binBps' # B/s +KIBI_BYTES_SEC = 'KiBs' # KiB/s +MEBI_BYTES_SEC = 'MiBs' # MiB/s +GIBI_BYTES_SEC = 'GiBs' # GiB/s +TEBI_BYTES_SEC = 'TiBs' # TiB/s +PEBI_BYTES_SEC = 'PiBs' # PB/s + BYTES_SEC = 'Bps' # B/s KILO_BYTES_SEC = 'KBs' # kB/s MEGA_BYTES_SEC = 'MBs' # MB/s GIGA_BYTES_SEC = 'GBs' # GB/s TERA_BYTES_SEC = 'TBs' # TB/s PETA_BYTES_SEC = 'PBs' # PB/s + +BITS_SEC_IEC = 'binbps' # b/s +KIBI_BITS_SEC = 'Kibits' # Kib/s +MEBI_BITS_SEC = 'Mibits' # Mib/s +GIBI_BITS_SEC = 'Gibits' # Gib/s +TEBI_BITS_SEC = 'Tibits' # Tib/s +PEBI_BITS_SEC = 'Pibits' # Pib/s + BITS_SEC = 'bps' # b/s KILO_BITS_SEC = 'Kbits' # kb/s MEGA_BITS_SEC = 'Mbits' # Mb/s GIGA_BITS_SEC = 'Gbits' # Gb/s TERA_BITS_SEC = 'Tbits' # Tb/s PETA_BITS_SEC = 'Pbits' # Pb/s +# Date & Time +DATE_TIME_ISO = 'dateTimeAsIso' +DATE_TIME_ISO_TODAY = 'dateTimeAsIsoNoDateIfToday' +DATE_TIME_US = 'dateTimeAsUS' +DATE_TIME_US_TODAY = 'dateTimeAsUSNoDateIfToday' +DATE_TIME_LOCAL = 'dateTimeAsLocal' +DATE_TIME_LOCAL_TODAY = 'dateTimeAsLocalNoDateIfToday' +DATE_TIME_DEFAULT = 'dateTimeAsSystem' +DATE_TIME_FROM_NOW = 'dateTimeFromNow' # Energy WATT = 'watt' # W KILO_WATT = 'kwatt' # kW MEGA_WATT = 'megwatt' # MW -GAGA_WATT = 'gwatt' # GW +GIGA_WATT = 'gwatt' # GW MILLI_WATT = 'mwatt' # mW WATT_SQUARE_METER = 'Wm2' # W/m² VOLT_AMPERE = 'voltamp' # VA @@ -107,6 +149,7 @@ WATT_HOUR = 'watth' # Wh WATT_HOUR_KILO = 'watthperkg' # Wh/kg KILO_WATT_HOUR = 'kwatth' # kWh +KILO_WATT_MIN = 'kwattm' # kWm AMPERE_HOUR = 'amph' # Ah KILO_AMPERE_HR = 'kamph' # kAh MILLI_AMPER_HOUR = 'mamph' # mAh @@ -156,10 +199,12 @@ # Mass MILLI_GRAM = 'massmg' # mg GRAM = 'massg' # g +POUND = 'masslb' # lb KILO_GRAM = 'masskg' # kg METRIC_TON = 'masst' # t # Length MILLI_METER = 'lengthmm' # mm +INCH = 'lengthin' # in METER = 'lengthm' # m KILO_METER = 'lengthkm' # km FEET = 'lengthft' # ft @@ -232,6 +277,10 @@ MILLI_LITRE = 'mlitre' # mL LITRE = 'litre' # L CUBIC_METER = 'm3' # m³ -NORMAIL_CUBIC_METER = 'Nm3' # Nm³ +NORMAL_CUBIC_METER = 'Nm3' # Nm³ CUBIC_DECI_METER = 'dm3' # dm³ GALLONS = 'gallons' # g +# Boolean +TRUE_FALSE = 'bool' # True/False +YES_NO = 'bool_yes_no' # Yes/No +ON_OFF = 'bool_on_off' # On/Off From 0ee9c989634c596bce4a7deeef5e4552db1619e7 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Tue, 24 May 2022 15:51:21 +0200 Subject: [PATCH 330/403] Update documentation regarding development (#497) - We support up to python 3.10 - Meetings were put on halt - Update on grafanalib maintenance Signed-off-by: Daniel Holbach --- README.rst | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.rst b/README.rst index 3c2e6079..a8b0941d 100644 --- a/README.rst +++ b/README.rst @@ -57,7 +57,7 @@ Support This library is in its very early stages. We'll probably make changes that break backwards compatibility, although we'll try hard not to. -grafanalib works with Python 3.6 through 3.9. +grafanalib works with Python 3.6 through 3.10. Developing ========== @@ -79,14 +79,13 @@ built-in way to do it. See https://grafana.com/docs/administration/provisioning Community ========= -We'd like you to join the ``grafanalib`` community! Talk to us on Slack (see the links), -or join us for one of our next meetings): +We currently don't follow a roadmap for ``grafanalib`` and both `maintainers +` have recently +become somewhat occupied otherwise. -- Meetings take place monthly: third Friday of the month 15:00 UTC -- https://weaveworks.zoom.us/j/96824669060 -- `Meeting minutes and agenda - `_ - (includes links to meeting recordings) +We'd like you to join the ``grafanalib`` community! If you would like to +help out maintaining ``grafanalib`` that would be great. It's a fairly laid-back +and straight-forward project. Please talk to us on Slack (see the links below). We follow the `CNCF Code of Conduct `_. From 2d11201575a2b60c768b66dfce9dca39c288f22e Mon Sep 17 00:00:00 2001 From: mharbison72 <57785103+mharbison72@users.noreply.github.com> Date: Tue, 24 May 2022 09:52:35 -0400 Subject: [PATCH 331/403] Small cleanup (#496) * Drop a RowPanel member re-declaration prior to usage Flagged by PyCharm. It looks like this snuck in with c665f21f107e. * Fix iteration over nested panels in a Dashboard This appears to be an obvious typo (after PyCharm flagged `row_panel` as unused), but it looks like this is only used by `auto_panel_ids()`. I wasn't able to generate a difference in output before and after this change when adding 2 `SingleStat` panels to a `RowPanel`, and in turn adding that to the dashboard. The changed code definitely runs in this case, so IDK what is ultimately making the changes to the nested panels prior to this. --- grafanalib/core.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index c0a5716d..6ef3dbc6 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1147,7 +1147,7 @@ def _iter_panels(self): if hasattr(panel, 'panels'): yield panel for row_panel in panel._iter_panels(): - yield panel + yield row_panel else: yield panel @@ -1325,7 +1325,6 @@ class RowPanel(Panel): :param collapsed: set True if row should be collapsed :param panels: list of panels in the row, only to be used when collapsed=True """ - collapsed = attr.ib(default=False, validator=instance_of(bool)) panels = attr.ib(default=attr.Factory(list), validator=instance_of(list)) collapsed = attr.ib(default=False, validator=instance_of(bool)) From 2d9af3546282fe9ebed82d0ba06f847a6b789e83 Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Wed, 25 May 2022 15:34:44 +0100 Subject: [PATCH 332/403] Fix broken links (#502) --- CHANGELOG.rst | 4 ++-- grafanalib/core.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a16b3579..ff1af03e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,7 +10,7 @@ x.x.x (TBC) * Added missing units for Boolean, Data, Data Rate, Date & Time, Energy, Length, Mass, and Misc * Fix typo in unit constant ``GIGA_WATT`` (was ``GAGA_WATT``) -* Fix typo in unit constant ``NORMAL_CUBIC_METER`` (was ``NORMAIL_CUBIC_METER``) +* Fix typo in unit constant ``NORMAL_CUBIC_METER`` (was ``NORMAIL_CUBIC_METER``) 0.6.3 (2022-03-30) @@ -79,7 +79,7 @@ Changes * Added colour overrides to pie chart panel * Added missing attributes from xAxis class -* Added transformations for the Panel class (https://grafana.com/docs/grafana/next/panels/reference-transformation-functions/) +* Added transformations for the Panel class (https://grafana.com/docs/grafana/latest/panels/reference-transformation-functions/) * Added Worldmap panel (https://grafana.com/grafana/plugins/grafana-worldmap-panel/) * Added missing fill gradient to Graph panel * Added missing align to graph panel diff --git a/grafanalib/core.py b/grafanalib/core.py index 6ef3dbc6..ed611641 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1556,7 +1556,7 @@ class TimeSeries(Panel): :param legendCalcs: which calculations should be displayed in the legend. Defaults to an empty list. Possible values are: allIsNull, allIsZero, changeCount, count, delta, diff, diffperc, distinctCount, firstNotNull, max, mean, min, logmin, range, step, total. For more information see - https://grafana.com/docs/grafana/next/panels/reference-calculation-types/ + https://grafana.com/docs/grafana/next/panels/calculation-types/ :param lineInterpolation: line interpolation linear (Default), smooth, stepBefore, stepAfter :param lineWidth: line width, default 1 From 181b6c9d69dc88b4524ad008b9212932de750f49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 May 2022 13:53:14 +0100 Subject: [PATCH 333/403] Bump sphinx from 4.5.0 to 5.0.0 (#503) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 4.5.0 to 5.0.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v4.5.0...v5.0.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 5e82e6eb..bec15187 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 4.5.0 +sphinx == 5.0.0 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From e3869835fc74766f57c154c1a81a9f7234403f5c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Sep 2022 08:45:15 +0100 Subject: [PATCH 334/403] Bump lycheeverse/lychee-action from 1.4.1 to 1.5.1 (#517) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.4.1 to 1.5.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.4.1...v1.5.1) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index eacf4569..c1814a24 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.4.1 + uses: lycheeverse/lychee-action@v1.5.1 with: args: --verbose **/*.html - name: Fail if there were link errors From 5588dc91854fbcbe61c53d0464ac568725a54e78 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Sep 2022 08:45:33 +0100 Subject: [PATCH 335/403] Bump sphinx from 5.0.0 to 5.1.1 (#518) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.0.0 to 5.1.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.0.0...v5.1.1) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index bec15187..c8e3697c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 5.0.0 +sphinx == 5.1.1 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From 0e12dd0a2f14b677393270ddc9b72f92981a8707 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Sep 2022 08:45:50 +0100 Subject: [PATCH 336/403] Bump actions/setup-python from 3.1.2 to 4.2.0 (#519) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 3.1.2 to 4.2.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v3.1.2...v4.2.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index c1814a24..9f2516d2 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3.1.2 + uses: actions/setup-python@v4.2.0 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 28efddd6..e71b8cb5 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.7 - uses: actions/setup-python@v3.1.2 + uses: actions/setup-python@v4.2.0 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6d36d185..fb1fc2ae 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3.1.2 + uses: actions/setup-python@v4.2.0 with: python-version: ${{ matrix.python }} - name: Run tests From a3f51a93a570f73c1081d3c6540f318ef561f0b1 Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Fri, 9 Sep 2022 08:53:36 +0100 Subject: [PATCH 337/403] Fix broken links (#527) --- grafanalib/humio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/humio.py b/grafanalib/humio.py index 4e3609ab..6bff77a8 100644 --- a/grafanalib/humio.py +++ b/grafanalib/humio.py @@ -10,7 +10,7 @@ class HumioTarget(object): Link to Humio Grafana plugin https://grafana.com/grafana/plugins/humio-datasource/ - Humio docs on query language https://docs.humio.com/reference/language-syntax/ + Humio docs on query language https://library.humio.com/humio-server/syntax.html :param humioQuery: Query that will be executed on Humio :param humioRepository: Repository to execute query on. From bf2d3cb1ea6d9a465d5db02f8ebd9581d638f601 Mon Sep 17 00:00:00 2001 From: Matt Harbison <57785103+mharbison72@users.noreply.github.com> Date: Fri, 9 Sep 2022 03:58:27 -0400 Subject: [PATCH 338/403] Add support for Shared Tooltip on graphs (fixes #476) (#500) --- grafanalib/core.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index ed611641..f4fc53c9 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -262,6 +262,10 @@ def to_json_data(self): GAUGE_DISPLAY_MODE_LCD = 'lcd' GAUGE_DISPLAY_MODE_GRADIENT = 'gradient' +GRAPH_TOOLTIP_MODE_NOT_SHARED = 0 +GRAPH_TOOLTIP_MODE_SHARED_CROSSHAIR = 1 +GRAPH_TOOLTIP_MODE_SHARED_TOOLTIP = 2 # Shared crosshair AND tooltip + DEFAULT_AUTO_COUNT = 30 DEFAULT_MIN_AUTO_INTERVAL = '10s' @@ -1105,6 +1109,14 @@ class Dashboard(object): validator=instance_of(bool), ) gnetId = attr.ib(default=None) + + # Documented in Grafana 6.1.6, and obsoletes sharedCrosshair. Requires a + # newer schema than the current default of 12. + graphTooltip = attr.ib( + default=GRAPH_TOOLTIP_MODE_NOT_SHARED, + validator=instance_of(int), + ) + hideControls = attr.ib( default=False, validator=instance_of(bool), @@ -1185,6 +1197,7 @@ def to_json_data(self): 'description': self.description, 'editable': self.editable, 'gnetId': self.gnetId, + 'graphTooltip': self.graphTooltip, 'hideControls': self.hideControls, 'id': self.id, 'links': self.links, From 5d842fe292ddad651ca2414d67d160cedeaa1e2a Mon Sep 17 00:00:00 2001 From: Oscar van Leusen Date: Fri, 9 Sep 2022 09:18:54 +0100 Subject: [PATCH 339/403] fix: target validation (#520) * Fix target refId validation * Update test_core.py Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- grafanalib/core.py | 6 +++--- grafanalib/tests/test_core.py | 22 ++++++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index f4fc53c9..3c1ea5d4 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -395,10 +395,10 @@ def to_json_data(self): def is_valid_target(instance, attribute, value): """ - Check if a given attribute is a valid target + Check if a given attribute is a valid Target """ - if not hasattr(value, "refId"): - raise ValueError(f"{attribute.name} should have 'refId' attribute") + if value.refId == "": + raise ValueError(f"{attribute.name} should have non-empty 'refId' attribute") @attr.s diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index e7a2c258..5c2aa839 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -26,7 +26,9 @@ def dummy_evaluator() -> G.Evaluator: def dummy_alert_condition() -> G.AlertCondition: return G.AlertCondition( - target=G.Target(), + target=G.Target( + refId="A", + ), evaluator=G.Evaluator( type=G.EVAL_GT, params=42), @@ -430,7 +432,7 @@ def test_graph_panel_alert(): targets = ['dummy_prom_query'] title = 'dummy title' alert = [ - G.AlertCondition(G.Target(), G.Evaluator('a', 'b'), G.TimeRange('5', '6'), 'd', 'e') + G.AlertCondition(G.Target(refId="A"), G.Evaluator('a', 'b'), G.TimeRange('5', '6'), 'd', 'e') ] thresholds = [ G.GraphThreshold(20.0), @@ -698,6 +700,22 @@ def test_histogram(): assert data['options']['bucketSize'] == bucketSize +def test_target_invalid(): + with pytest.raises(ValueError, match=r"target should have non-empty 'refId' attribute"): + return G.AlertCondition( + target=G.Target(), + evaluator=G.Evaluator( + type=G.EVAL_GT, + params=42), + timeRange=G.TimeRange( + from_time='5m', + to_time='now' + ), + operator=G.OP_AND, + reducerType=G.RTYPE_AVG, + ) + + def test_sql_target(): t = G.Table( dataSource="some data source", From b45ae5d1cdd2899eaebf23c00637fa3b16170da9 Mon Sep 17 00:00:00 2001 From: Jozsef Pohl <44181369+trewallier@users.noreply.github.com> Date: Mon, 12 Sep 2022 14:29:18 +0200 Subject: [PATCH 340/403] Add support for Ae3e Plotly panel (#528) --- CHANGELOG.rst | 1 + grafanalib/core.py | 45 +++++++++++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 26 ++++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ff1af03e..23980ee7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ x.x.x (TBC) =========== * Added ... +* Added ae3e plotly panel support * Added datasource parameter to Influxdb targets * Added missing units for Boolean, Data, Data Rate, Date & Time, Energy, Length, Mass, and Misc diff --git a/grafanalib/core.py b/grafanalib/core.py index 3c1ea5d4..7f37b63e 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -97,6 +97,7 @@ def to_json_data(self): WORLD_MAP_TYPE = 'grafana-worldmap-panel' NEWS_TYPE = 'news' HISTOGRAM_TYPE = 'histogram' +AE3E_PLOTLY_TYPE = 'ae3e-plotly-panel' DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -3634,3 +3635,47 @@ def to_json_data(self): 'type': NEWS_TYPE, } ) + + +@attr.s +class Ae3ePlotly(Panel): + """Generates ae3e plotly panel json structure + GitHub repo of the panel: https://github.com/ae3e/ae3e-plotly-panel + :param configuration in json format: Plotly configuration. Docs: https://plotly.com/python/configuration-options/ + :param data: Plotly data: https://plotly.com/python/figure-structure/ + :param layout: Layout of the chart in json format. Plotly docs: https://plotly.com/python/reference/layout/ + :param script: Script executed whenever new data is available. Must return an object with one or more of the + following properties : data, layout, config f(data, variables){...your code...} + :param clickScript: Script executed when chart is clicked. f(data){...your code...} + """ + configuration = attr.ib(default=attr.Factory(dict), validator=attr.validators.instance_of(dict)) + data = attr.ib(default=attr.Factory(list), validator=instance_of(list)) + layout = attr.ib(default=attr.Factory(dict), validator=attr.validators.instance_of(dict)) + script = attr.ib(default="""console.log(data) + var trace = { + x: data.series[0].fields[0].values.buffer, + y: data.series[0].fields[1].values.buffer + }; + return {data:[trace],layout:{title:'My Chart'}};""", validator=instance_of(str)) + clickScript = attr.ib(default='', validator=instance_of(str)) + + def to_json_data(self): + plotly = self.panel_json( + { + 'fieldConfig': { + 'defaults': {}, + 'overrides': [] + }, + 'options': { + 'configuration': {}, + 'data': self.data, + 'layout': {}, + 'onclick': self.clickScript, + 'script': self.script, + }, + 'type': AE3E_PLOTLY_TYPE, + } + ) + _deep_update(plotly["options"]["layout"], self.layout) + _deep_update(plotly["options"]["configuration"], self.configuration) + return plotly diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 5c2aa839..5252d6e1 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -700,6 +700,32 @@ def test_histogram(): assert data['options']['bucketSize'] == bucketSize +def test_ae3e_plotly(): + data_source = "dummy data source" + targets = ["dummy_prom_query"] + title = "dummy title" + panel = G.Ae3ePlotly(data_source, targets, title) + data = panel.to_json_data() + assert data["targets"] == targets + assert data["datasource"] == data_source + assert data["title"] == title + assert bool(data["options"]["configuration"]) is False + assert bool(data["options"]["layout"]) is False + + config = { + "displayModeBar": False + } + layout = { + "font": { + "color": "darkgrey" + }, + } + panel = G.Ae3ePlotly(data_source, targets, title, configuration=config, layout=layout) + data = panel.to_json_data() + assert data["options"]["configuration"] == config + assert data["options"]["layout"] == layout + + def test_target_invalid(): with pytest.raises(ValueError, match=r"target should have non-empty 'refId' attribute"): return G.AlertCondition( From 89c9fe6335a140757bc2a0bbae90a81a46280c2d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 15:26:52 +0100 Subject: [PATCH 341/403] Bump sphinx from 5.1.1 to 5.2.1 (#530) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.1.1 to 5.2.1. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.1.1...v5.2.1) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index c8e3697c..d80dfd8f 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 5.1.1 +sphinx == 5.2.1 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From 329229276c76b7aea35626875fe3ed24a43457f0 Mon Sep 17 00:00:00 2001 From: milkpirate Date: Thu, 13 Oct 2022 09:52:27 +0200 Subject: [PATCH 342/403] Add ePict Plugin (#512) * update ignores * LF * add type * add epict box * add tests * add epict panel * add tests * correct panel type * corret logic / better usability * upd chglg * cleanup * restore accidentally deleted files Signed-off-by: Paul Schroeder * upd chg log Signed-off-by: Paul Schroeder Signed-off-by: Paul Schroeder --- .gitignore | 2 +- CHANGELOG.rst | 2 + grafanalib/core.py | 166 ++++++++++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 159 ++++++++++++++++++++++++++++++++ 4 files changed, 328 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4edeec2b..7333a110 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ test-results/junit-*.xml .ensure-* /.tox /.coverage -/venv/ +/venv*/ /.idea/ # Documentation diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 23980ee7..209f6c9c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ x.x.x (TBC) =========== * Added ... +* Added ePict_ plugin. * Added ae3e plotly panel support * Added datasource parameter to Influxdb targets * Added missing units for Boolean, Data, Data Rate, Date & Time, Energy, Length, @@ -13,6 +14,7 @@ x.x.x (TBC) * Fix typo in unit constant ``GIGA_WATT`` (was ``GAGA_WATT``) * Fix typo in unit constant ``NORMAL_CUBIC_METER`` (was ``NORMAIL_CUBIC_METER``) +.. _ePict: basehttps://grafana.com/grafana/plugins/larona-epict-panel/ 0.6.3 (2022-03-30) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 7f37b63e..1c83dbec 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -78,6 +78,7 @@ def to_json_data(self): ROW_TYPE = 'row' GRAPH_TYPE = 'graph' DISCRETE_TYPE = 'natel-discrete-panel' +EPICT_TYPE = 'larona-epict-panel' STAT_TYPE = 'stat' SINGLESTAT_TYPE = 'singlestat' STATE_TIMELINE_TYPE = 'state-timeline' @@ -304,6 +305,136 @@ def to_json_data(self): VTYPE_DEFAULT = VTYPE_AVG +@attr.s +class ePictBox(object): + """ + ePict Box. + + :param angle: Rotation angle of box + :param backgroundColor: Dito + :param blinkHigh: Blink if below threshold + :param blinkLow: Blink if above threshold + :param color: Text color + :param colorHigh: High value color + :param colorLow: Low value color + :param colorMedium: In between value color + :param colorSymbol: Whether to enable background color for symbol + :param customSymbol: URL to custom symbol (will set symbol to "custom" if set) + :param decimal: Number of decimals + :param fontSize: Dito + :param hasBackground: Whether to enable background color for text + :param hasOrb: Whether an orb should be displayed + :param hasSymbol: Whether a (custom) symbol should be displayed + :param isUsingThresholds: Whether to enable thresholds. + :param orbHideText: Whether to hide text next to orb + :param orbLocation: Orb location (choose from 'Left', 'Right', 'Top' or 'Bottom') + :param orbSize: Dito + :param prefix: Value prefix to be displayed (e.g. °C) + :param prefixSize: Dito + :param selected: Dont know + :param serie: Which series to use data from + :param suffix: Value suffix to be displayed + :param suffixSize: Dito + :param symbol: Automatically placed by the plugin format: `data:image/svg+xml;base64,`, check manually. + :param symbolDefHeight: Dont know + :param symbolDefWidth: Dont know + :param symbolHeight: Dito + :param symbolHideText: Whether to hide value text next to symbol + :param symbolWidth: Dito + :param text: Dont know + :param thresholds: Coloring thresholds: Enter 2 + comma-separated numbers. 20,60 will produce: value <= 20 -> green; + value between 20 and 60 -> yellow; value >= 60 -> red. If set, it will also set + isUsingThresholds to True + :param url: URL to open when clicked on + :param xpos: X in (0, X size of image) + :param ypos: Y in (0, Y size of image) + """ + + angle = attr.ib(default=0, validator=instance_of(int)) + backgroundColor = attr.ib(default="#000", validator=instance_of((RGBA, RGB, str))) + blinkHigh = attr.ib(default=False, validator=instance_of(bool)) + blinkLow = attr.ib(default=False, validator=instance_of(bool)) + color = attr.ib(default="#000", validator=instance_of((RGBA, RGB, str))) + colorHigh = attr.ib(default="#000", validator=instance_of((RGBA, RGB, str))) + colorLow = attr.ib(default="#000", validator=instance_of((RGBA, RGB, str))) + colorMedium = attr.ib(default="#000", validator=instance_of((RGBA, RGB, str))) + colorSymbol = attr.ib(default=False, validator=instance_of(bool)) + customSymbol = attr.ib(default="", validator=instance_of(str)) + decimal = attr.ib(default=0, validator=instance_of(int)) + fontSize = attr.ib(default=12, validator=instance_of(int)) + hasBackground = attr.ib(default=False, validator=instance_of(bool)) + hasOrb = attr.ib(default=False, validator=instance_of(bool)) + hasSymbol = attr.ib(default=False, validator=instance_of(bool)) + isUsingThresholds = attr.ib(default=False, validator=instance_of(bool)) + orbHideText = attr.ib(default=False, validator=instance_of(bool)) + orbLocation = attr.ib( + default="Left", + validator=in_(['Left', 'Right', 'Top', 'Bottom']) + ) + orbSize = attr.ib(default=13, validator=instance_of(int)) + prefix = attr.ib(default="", validator=instance_of(str)) + prefixSize = attr.ib(default=10, validator=instance_of(int)) + selected = attr.ib(default=False, validator=instance_of(bool)) + serie = attr.ib(default="", validator=instance_of(str)) + suffix = attr.ib(default="", validator=instance_of(str)) + suffixSize = attr.ib(default=10, validator=instance_of(int)) + symbol = attr.ib(default="", validator=instance_of(str)) + symbolDefHeight = attr.ib(default=32, validator=instance_of(int)) + symbolDefWidth = attr.ib(default=32, validator=instance_of(int)) + symbolHeight = attr.ib(default=32, validator=instance_of(int)) + symbolHideText = attr.ib(default=False, validator=instance_of(bool)) + symbolWidth = attr.ib(default=32, validator=instance_of(int)) + text = attr.ib(default="N/A", validator=instance_of(str)) + thresholds = attr.ib(default="", validator=instance_of(str)) + url = attr.ib(default="", validator=instance_of(str)) + xpos = attr.ib(default=0, validator=instance_of(int)) + ypos = attr.ib(default=0, validator=instance_of(int)) + + def to_json_data(self): + self.symbol = "custom" if self.customSymbol else self.symbol + self.isUsingThresholds = bool(self.thresholds) + + return { + "angle": self.angle, + "backgroundColor": self.backgroundColor, + "blinkHigh": self.blinkHigh, + "blinkLow": self.blinkLow, + "color": self.color, + "colorHigh": self.colorHigh, + "colorLow": self.colorLow, + "colorMedium": self.colorMedium, + "colorSymbol": self.colorSymbol, + "customSymbol": self.customSymbol, + "decimal": self.decimal, + "fontSize": self.fontSize, + "hasBackground": self.hasBackground, + "hasOrb": self.hasOrb, + "hasSymbol": self.hasSymbol, + "isUsingThresholds": self.isUsingThresholds, + "orbHideText": self.orbHideText, + "orbLocation": self.orbLocation, + "orbSize": self.orbSize, + "prefix": self.prefix, + "prefixSize": self.prefixSize, + "selected": self.selected, + "serie": self.serie, + "suffix": self.suffix, + "suffixSize": self.suffixSize, + "symbol": self.symbol, + "symbolDefHeight": self.symbolDefHeight, + "symbolDefWidth": self.symbolDefWidth, + "symbolHeight": self.symbolHeight, + "symbolHideText": self.symbolHideText, + "symbolWidth": self.symbolWidth, + "text": self.text, + "thresholds": self.thresholds, + "url": self.url, + "xpos": self.xpos, + "ypos": self.ypos, + } + + @attr.s class Grid(object): @@ -1330,6 +1461,41 @@ def panel_json(self, overrides): return res +@attr.s +class ePict(Panel): + """ + Generates ePict panel json structure. + https://grafana.com/grafana/plugins/larona-epict-panel/ + + :param autoScale: Whether to auto scale image to panel size. + :param bgURL: Where to load the image from. + :param boxes: The info boxes to be placed on the image. + """ + + bgURL = attr.ib(default='', validator=instance_of(str)) + + autoScale = attr.ib(default=True, validator=instance_of(bool)) + boxes = attr.ib( + default=[], + validator=attr.validators.deep_iterable( + member_validator=instance_of(ePictBox), + iterable_validator=instance_of(list), + ), + ) + + def to_json_data(self): + graph_object = { + 'type': EPICT_TYPE, + + 'options': { + 'autoScale': self.autoScale, + 'bgURL': self.bgURL, + 'boxes': self.boxes + } + } + return self.panel_json(graph_object) + + @attr.s class RowPanel(Panel): """ diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 5252d6e1..c261a65f 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -151,6 +151,165 @@ def test_Text_exception_checks(): G.Text(mode=123) +def test_ePictBox(): + t = G.ePictBox() + json_data = t.to_json_data() + + assert json_data['angle'] == 0 + assert json_data['backgroundColor'] == "#000" + assert json_data['blinkHigh'] is False + assert json_data['blinkLow'] is False + assert json_data['color'] == "#000" + assert json_data['colorHigh'] == "#000" + assert json_data['colorLow'] == "#000" + assert json_data['colorMedium'] == "#000" + assert json_data['colorSymbol'] is False + assert json_data['customSymbol'] == "" + assert json_data['decimal'] == 0 + assert json_data['fontSize'] == 12 + assert json_data['hasBackground'] is False + assert json_data['hasOrb'] is False + assert json_data['hasSymbol'] is False + assert json_data['isUsingThresholds'] is False + assert json_data['orbHideText'] is False + assert json_data['orbLocation'] == "Left" + assert json_data['orbSize'] == 13 + assert json_data['prefix'] == "" + assert json_data['prefixSize'] == 10 + assert json_data['selected'] is False + assert json_data['serie'] == "" + assert json_data['suffix'] == "" + assert json_data['suffixSize'] == 10 + assert json_data['symbol'] == "" + assert json_data['symbolDefHeight'] == 32 + assert json_data['symbolDefWidth'] == 32 + assert json_data['symbolHeight'] == 32 + assert json_data['symbolHideText'] is False + assert json_data['symbolWidth'] == 32 + assert json_data['text'] == "N/A" + assert json_data['thresholds'] == "" + assert json_data['url'] == "" + assert json_data['xpos'] == 0 + assert json_data['ypos'] == 0 + + t = G.ePictBox( + angle=1, + backgroundColor="#100", + blinkHigh=True, + blinkLow=True, + color="#200", + colorHigh="#300", + colorLow="#400", + colorMedium="#500", + colorSymbol=True, + decimal=2, + fontSize=9, + hasBackground=True, + hasOrb=True, + hasSymbol=True, + orbHideText=True, + orbLocation="Right", + orbSize=10, + prefix="prefix", + prefixSize=11, + selected=True, + serie="series", + suffix="suffix", + suffixSize=12, + symbol="data:image/svg+xml;base64,...", + symbolDefHeight=13, + symbolDefWidth=14, + symbolHeight=15, + symbolHideText=True, + symbolWidth=17, + text="text", + thresholds="40,50", + url="https://google.de", + xpos=18, + ypos=19, + ) + + json_data = t.to_json_data() + + assert json_data['angle'] == 1 + assert json_data['backgroundColor'] == "#100" + assert json_data['blinkHigh'] is True + assert json_data['blinkLow'] is True + assert json_data['color'] == "#200" + assert json_data['colorHigh'] == "#300" + assert json_data['colorLow'] == "#400" + assert json_data['colorMedium'] == "#500" + assert json_data['colorSymbol'] is True + assert json_data['decimal'] == 2 + assert json_data['fontSize'] == 9 + assert json_data['hasBackground'] is True + assert json_data['hasOrb'] is True + assert json_data['hasSymbol'] is True + assert json_data['isUsingThresholds'] is True + assert json_data['orbHideText'] is True + assert json_data['orbLocation'] == "Right" + assert json_data['orbSize'] == 10 + assert json_data['prefix'] == "prefix" + assert json_data['prefixSize'] == 11 + assert json_data['selected'] is True + assert json_data['serie'] == "series" + assert json_data['suffix'] == "suffix" + assert json_data['suffixSize'] == 12 + assert json_data['symbol'] == "data:image/svg+xml;base64,..." + assert json_data['symbolDefHeight'] == 13 + assert json_data['symbolDefWidth'] == 14 + assert json_data['symbolHeight'] == 15 + assert json_data['symbolHideText'] is True + assert json_data['symbolWidth'] == 17 + assert json_data['text'] == "text" + assert json_data['thresholds'] == "40,50" + assert json_data['url'] == "https://google.de" + assert json_data['xpos'] == 18 + assert json_data['ypos'] == 19 + + +def test_ePictBox_custom_symbole_logic(): + t = G.ePictBox( + customSymbol="https://foo.bar/foo.jpg", + symbol="will be overiden", + ) + + json_data = t.to_json_data() + + assert json_data['customSymbol'] == "https://foo.bar/foo.jpg" + assert json_data['symbol'] == "custom" + + +def test_ePict(): + t = G.ePict() + json_data = t.to_json_data() + + assert json_data['type'] == G.EPICT_TYPE + assert json_data['options']['autoScale'] is True + assert json_data['options']['bgURL'] == '' + assert json_data['options']['boxes'] == [] + + t = G.ePict( + autoScale=False, + bgURL='https://example.com/img.jpg', + boxes=[ + G.ePictBox(), + G.ePictBox(angle=123), + ] + ) + json_data = t.to_json_data() + + print(json_data) + + assert json_data['type'] == G.EPICT_TYPE + assert json_data['options']['autoScale'] is False + assert json_data['options']['bgURL'] == 'https://example.com/img.jpg' + assert json_data['options']['boxes'] == [ + G.ePictBox(), + G.ePictBox(angle=123), + ] + + def test_Text(): t = G.Text() From 04766e1a1279d535dcc8fd02b52afbf27f9617b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 08:52:41 +0100 Subject: [PATCH 343/403] Bump actions/setup-python from 4.2.0 to 4.3.0 (#533) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.2.0 to 4.3.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4.2.0...v4.3.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 9f2516d2..0c04835f 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v4.2.0 + uses: actions/setup-python@v4.3.0 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index e71b8cb5..d66bb0c0 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.7 - uses: actions/setup-python@v4.2.0 + uses: actions/setup-python@v4.3.0 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index fb1fc2ae..6db9f740 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v4.2.0 + uses: actions/setup-python@v4.3.0 with: python-version: ${{ matrix.python }} - name: Run tests From fcbc30ebf2c8c475aa21931b91979b1eadf91f30 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Oct 2022 08:52:58 +0100 Subject: [PATCH 344/403] Bump sphinx from 5.2.1 to 5.2.3 (#532) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.2.1 to 5.2.3. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/5.x/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.2.1...v5.2.3) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index d80dfd8f..81c9b5e9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 5.2.1 +sphinx == 5.2.3 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From 721f0e0a0cadc6993edb8dc20a7ffa821dec0b48 Mon Sep 17 00:00:00 2001 From: Oscar van Leusen Date: Fri, 14 Oct 2022 09:11:03 +0100 Subject: [PATCH 345/403] Grafana 8.x new Alert Rule (#523) * Added Grafana 8.x new Alert Rule * [ADD] test for AlertRuler [ADD] Doc String on AlertRuler [UPD] Annotations Management [ADD] Default value * [FIX] test on AlertRuler * [UPD] uid on AlertRuler optional * [FIX] Test on AlertRuler * [FIX] Prettify code around the test * [FIX] Unit test of Alert Ruler Class * Finish AlertRule type and add AlertGroup type for Grafana 8.x alerts. Add unit tests, example usage, example upload script, and console generate-alertgroup(s) commands * Fix failing flake8 * add some getting started documentation Co-authored-by: Vincent CANDEAU Co-authored-by: Nikita Lozhnikov Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- .gitignore | 1 + CHANGELOG.rst | 2 + docs/getting-started.rst | 48 ++++- grafanalib/_gen.py | 171 ++++++++++++--- grafanalib/core.py | 194 ++++++++++++++++-- grafanalib/elasticsearch.py | 2 +- .../tests/examples/example.alertgroup.py | 93 +++++++++ .../tests/examples/example.upload-alerts.py | 61 ++++++ grafanalib/tests/test_core.py | 116 +++++++++++ grafanalib/tests/test_examples.py | 18 +- setup.py | 4 +- 11 files changed, 656 insertions(+), 54 deletions(-) create mode 100644 grafanalib/tests/examples/example.alertgroup.py create mode 100644 grafanalib/tests/examples/example.upload-alerts.py diff --git a/.gitignore b/.gitignore index 7333a110..80bdfe77 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ test-results/junit-*.xml /.coverage /venv*/ /.idea/ +/.vscode/ # Documentation docs/build diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 209f6c9c..035a07aa 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,7 @@ x.x.x (TBC) =========== * Added ... +* Added Grafana 8.x new Alert Rule * Added ePict_ plugin. * Added ae3e plotly panel support * Added datasource parameter to Influxdb targets @@ -16,6 +17,7 @@ x.x.x (TBC) .. _ePict: basehttps://grafana.com/grafana/plugins/larona-epict-panel/ + 0.6.3 (2022-03-30) ================== diff --git a/docs/getting-started.rst b/docs/getting-started.rst index f9f7298a..cace4a4d 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -41,7 +41,7 @@ If you save the above as ``example.dashboard.py`` (the suffix must be $ generate-dashboard -o frontend.json example.dashboard.py -Generating dashboards from code +Uploading dashboards from code =============================== Sometimes you may need to generate and upload dashboard directly from Python @@ -50,6 +50,52 @@ code. The following example provides minimal code boilerplate for it: .. literalinclude:: ../grafanalib/tests/examples/example.upload-dashboard.py :language: python +Writing Alerts +================== + +The following will configure a couple of alerts inside a group. + +.. literalinclude:: ../grafanalib/tests/examples/example.alertgroup.py + :language: python + +Although this example has a fair amount of boilerplate, when creating large numbers +of similar alerts it can save lots of time to programatically fill these fields. + +Each `AlertGroup` represents a folder within Grafana's alerts tab. This consists +of one or more `AlertRule`, which contains one or more triggers. Triggers define +what will cause the alert to fire. + +A trigger is made up of a `Target` (a Grafana query on a datasource) and an +`AlertCondition` (a condition this query must satisfy in order to alert). + +Finally, there are additional settings like: +* How the alert will behave when data sources have problems (`noDataAlertState` and `errorAlertState`) +* How frequently the trigger is evaluated (`evaluateInterval`) +* How long the AlertCondition needs to be met before the alert fires (`evaluateFor`) +* Annotations and labels, which help provide contextual information and direct where + your alerts will go + +Generating Alerts +===================== + +If you save the above as ``example.alertgroup.py`` (the suffix must be +``.alertgroup.py``), you can then generate the JSON dashboard with: + +.. code-block:: console + + $ generate-alertgroups -o alerts.json example.alertgroup.py + +Uploading alerts from code +=============================== + +As Grafana does not currently have a user interface for importing alertgroup JSON, +you must upload the alerts via Grafana's REST API. + +The following example provides minimal code boilerplate for it: + +.. literalinclude:: ../grafanalib/tests/examples/example.upload-alerts.py + :language: python + Installation ============ diff --git a/grafanalib/_gen.py b/grafanalib/_gen.py index 45653512..4094edf1 100644 --- a/grafanalib/_gen.py +++ b/grafanalib/_gen.py @@ -7,57 +7,163 @@ DASHBOARD_SUFFIX = '.dashboard.py' +ALERTGROUP_SUFFIX = '.alertgroup.py' + +""" +Common generation functionality +""" + + +class DashboardEncoder(json.JSONEncoder): + """Encode dashboard objects.""" + + def default(self, obj): + to_json_data = getattr(obj, 'to_json_data', None) + if to_json_data: + return to_json_data() + return json.JSONEncoder.default(self, obj) class DashboardError(Exception): """Raised when there is something wrong with a dashboard.""" -def load_dashboard(path): - """Load a ``Dashboard`` from a Python definition. +class AlertGroupError(Exception): + """Raised when there is something wrong with an alertgroup.""" + + +def write_dashboard(dashboard, stream): + json.dump( + dashboard.to_json_data(), stream, sort_keys=True, indent=2, + cls=DashboardEncoder) + stream.write('\n') + + +write_alertgroup = write_dashboard + + +class DefinitionError(Exception): + """Raised when there is a problem loading a Grafanalib type from a python definition.""" - :param str path: Path to a *.dashboard.py file that defines a variable, - ``dashboard``. - :return: A ``Dashboard`` + +def loader(path): + """Load a grafanalib type from a Python definition. + + :param str path: Path to a *..py file that defines a variable called . """ + gtype = path.split(".")[-2] + if sys.version_info[0] == 3 and sys.version_info[1] >= 5: import importlib.util - spec = importlib.util.spec_from_file_location('dashboard', path) + spec = importlib.util.spec_from_file_location(gtype, path) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) elif sys.version_info[0] == 3 and (sys.version_info[1] >= 3 or sys.version_info[1] <= 4): from importlib.machinery import SourceFileLoader - module = SourceFileLoader("dashboard", path).load_module() + module = SourceFileLoader(gtype, path).load_module() elif sys.version_info[0] == 2: import imp - module = imp.load_source('dashboard', path) + module = imp.load_source(gtype, path) else: import importlib - module = importlib.load_source('dashboard', path) + module = importlib.load_source(gtype, path) marker = object() - dashboard = getattr(module, 'dashboard', marker) - if dashboard is marker: - raise DashboardError( - "Dashboard definition {} does not define 'dashboard'".format(path)) - return dashboard + grafanalibtype = getattr(module, gtype, marker) + if grafanalibtype is marker: + raise DefinitionError( + "Definition {} does not define a variable '{}'".format(path, gtype)) + return grafanalibtype -class DashboardEncoder(json.JSONEncoder): - """Encode dashboard objects.""" +def run_script(f): + sys.exit(f(sys.argv[1:])) - def default(self, obj): - to_json_data = getattr(obj, 'to_json_data', None) - if to_json_data: - return to_json_data() - return json.JSONEncoder.default(self, obj) +""" +AlertGroup generation +""" -def write_dashboard(dashboard, stream): - json.dump( - dashboard.to_json_data(), stream, sort_keys=True, indent=2, - cls=DashboardEncoder) - stream.write('\n') + +def print_alertgroup(dashboard): + write_dashboard(dashboard, stream=sys.stdout) + + +def write_alertgroups(paths): + for path in paths: + assert path.endswith(ALERTGROUP_SUFFIX) + dashboard = loader(path) + with open(get_alertgroup_json_path(path), 'w') as json_file: + write_dashboard(dashboard, json_file) + + +def get_alertgroup_json_path(path): + assert path.endswith(ALERTGROUP_SUFFIX) + return '{}.json'.format(path[:-len(ALERTGROUP_SUFFIX)]) + + +def alertgroup_path(path): + abspath = os.path.abspath(path) + if not abspath.endswith(ALERTGROUP_SUFFIX): + raise argparse.ArgumentTypeError( + 'AlertGroup file {} does not end with {}'.format( + path, ALERTGROUP_SUFFIX)) + return abspath + + +def generate_alertgroups(args): + """Script for generating multiple alertgroups at a time""" + parser = argparse.ArgumentParser(prog='generate-alertgroups') + parser.add_argument( + 'alertgroups', metavar='ALERT', type=os.path.abspath, + nargs='+', help='Path to alertgroup definition', + ) + opts = parser.parse_args(args) + try: + write_alertgroups(opts.alertgroups) + except AlertGroupError as e: + sys.stderr.write('ERROR: {}\n'.format(e)) + return 1 + return 0 + + +def generate_alertgroup(args): + parser = argparse.ArgumentParser(prog='generate-alertgroup') + parser.add_argument( + '--output', '-o', type=os.path.abspath, + help='Where to write the alertgroup JSON' + ) + parser.add_argument( + 'alertgroup', metavar='ALERT', type=os.path.abspath, + help='Path to alertgroup definition', + ) + opts = parser.parse_args(args) + try: + alertgroup = loader(opts.alertgroup) + if not opts.output: + print_alertgroup(alertgroup) + else: + with open(opts.output, 'w') as output: + write_alertgroup(alertgroup, output) + except AlertGroupError as e: + sys.stderr.write('ERROR: {}\n'.format(e)) + return 1 + return 0 + + +def generate_alertgroups_script(): + """Entry point for generate-alertgroups.""" + run_script(generate_alertgroups) + + +def generate_alertgroup_script(): + """Entry point for generate-alertgroup.""" + run_script(generate_alertgroup) + + +""" +Dashboard generation +""" def print_dashboard(dashboard): @@ -66,12 +172,13 @@ def print_dashboard(dashboard): def write_dashboards(paths): for path in paths: - dashboard = load_dashboard(path) - with open(get_json_path(path), 'w') as json_file: + assert path.endswith(DASHBOARD_SUFFIX) + dashboard = loader(path) + with open(get_dashboard_json_path(path), 'w') as json_file: write_dashboard(dashboard, json_file) -def get_json_path(path): +def get_dashboard_json_path(path): assert path.endswith(DASHBOARD_SUFFIX) return '{}.json'.format(path[:-len(DASHBOARD_SUFFIX)]) @@ -113,7 +220,7 @@ def generate_dashboard(args): ) opts = parser.parse_args(args) try: - dashboard = load_dashboard(opts.dashboard) + dashboard = loader(opts.dashboard) if not opts.output: print_dashboard(dashboard) else: @@ -125,10 +232,6 @@ def generate_dashboard(args): return 0 -def run_script(f): - sys.exit(f(sys.argv[1:])) - - def generate_dashboards_script(): """Entry point for generate-dashboards.""" run_script(generate_dashboards) diff --git a/grafanalib/core.py b/grafanalib/core.py index 1c83dbec..f8e7d0ce 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -102,6 +102,8 @@ def to_json_data(self): DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' +DEFAULT_ALERT_EVALUATE_INTERVAL = '1m' +DEFAULT_ALERT_EVALUATE_FOR = '5m' DEFAULT_ROW_HEIGHT = Pixels(250) DEFAULT_LINE_WIDTH = 2 DEFAULT_POINT_RADIUS = 5 @@ -222,6 +224,12 @@ def to_json_data(self): ALERTLIST_STATE_ALERTING = 'alerting' ALERTLIST_STATE_PENDING = 'pending' +# Alert Rule state filter options (Grafana 8.x) +ALERTRULE_STATE_DATA_OK = 'OK' +ALERTRULE_STATE_DATA_NODATA = 'No Data' +ALERTRULE_STATE_DATA_ALERTING = 'Alerting' +ALERTRULE_STATE_DATA_ERROR = 'Error' + # Display Sort Order SORT_ASC = 1 SORT_DESC = 2 @@ -1126,13 +1134,13 @@ class AlertCondition(object): """ A condition on an alert. - :param Target target: Metric the alert condition is based on. + :param Target target: Metric the alert condition is based on. Not required at instantiation for Grafana 8.x alerts. :param Evaluator evaluator: How we decide whether we should alert on the metric. e.g. ``GreaterThan(5)`` means the metric must be greater than 5 to trigger the condition. See ``GreaterThan``, ``LowerThan``, ``WithinRange``, ``OutsideRange``, ``NoValue``. :param TimeRange timeRange: How long the condition must be true for before - we alert. + we alert. For Grafana 8.x alerts, this should be specified in the AlertRule instead. :param operator: One of ``OP_AND`` or ``OP_OR``. How this condition combines with other conditions. :param reducerType: RTYPE_* @@ -1150,25 +1158,30 @@ class AlertCondition(object): :param type: CTYPE_* """ - target = attr.ib(validator=is_valid_target) - evaluator = attr.ib(validator=instance_of(Evaluator)) - timeRange = attr.ib(validator=instance_of(TimeRange)) - operator = attr.ib() - reducerType = attr.ib() + target = attr.ib(default=None, validator=attr.validators.optional(is_valid_target)) + evaluator = attr.ib(default=None, validator=instance_of(Evaluator)) + timeRange = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(TimeRange))) + operator = attr.ib(default=OP_AND) + reducerType = attr.ib(default=RTYPE_LAST) + type = attr.ib(default=CTYPE_QUERY, kw_only=True) + def __get_query_params(self): + # Grafana 8.x alerts do not put the time range in the query params. + if self.useNewAlerts: + return [self.target.refId] + + return [self.target.refId, self.timeRange.from_time, self.timeRange.to_time] + def to_json_data(self): - queryParams = [ - self.target.refId, self.timeRange.from_time, self.timeRange.to_time - ] - return { - 'evaluator': self.evaluator, + condition = { + 'evaluator': self.evaluator.to_json_data(), 'operator': { 'type': self.operator, }, 'query': { - 'model': self.target, - 'params': queryParams, + 'model': self.target.to_json_data(), + 'params': self.__get_query_params(), }, 'reducer': { 'params': [], @@ -1177,6 +1190,12 @@ def to_json_data(self): 'type': self.type, } + # Grafana 8.x alerts do not put the target inside the alert condition. + if self.useNewAlerts: + del condition['query']['model'] + + return condition + @attr.s class Alert(object): @@ -1216,6 +1235,153 @@ def to_json_data(self): } +@attr.s +class AlertGroup(object): + """ + Create an alert group of Grafana 8.x alerts + + :param name: Alert group name + :param rules: List of AlertRule + """ + name = attr.ib() + rules = attr.ib(default=attr.Factory(list), validator=instance_of(list)) + + def group_rules(self, rules): + grouped_rules = [] + for each in rules: + each.rule_group = self.name + grouped_rules.append(each.to_json_data()) + return grouped_rules + + def to_json_data(self): + return { + 'name': self.name, + 'interval': "1m", + 'rules': self.group_rules(self.rules), + } + + +def is_valid_triggers(instance, attribute, value): + """Validator for AlertRule triggers""" + for trigger in value: + if not isinstance(trigger, tuple): + raise ValueError(f"{attribute.name} must be a list of [(Target, AlertCondition)] tuples") + + if list(map(type, trigger)) != [Target, AlertCondition]: + raise ValueError(f"{attribute.name} must be a list of [(Target, AlertCondition)] tuples") + + is_valid_target(instance, "alert trigger target", trigger[0]) + + +@attr.s +class AlertRule(object): + """ + Create a Grafana 8.x+ Alert Rule + + :param title: The alert's title, must be unique per folder + :param triggers: A list of Target and AlertCondition tuples, [(Target, AlertCondition)]. + The Target specifies the query, and the AlertCondition specifies how this is used to alert. + Several targets and conditions can be defined, alerts can fire based on all conditions + being met by specifying OP_AND on all conditions, or on any single condition being met + by specifying OP_OR on all conditions. + :param annotations: Summary and annotations + :param labels: Custom Labels for the metric, used to handle notifications + + :param evaluateInterval: The frequency of evaluation. Must be a multiple of 10 seconds. For example, 30s, 1m + :param evaluateFor: The duration for which the condition must be true before an alert fires + :param noDataAlertState: Alert state if no data or all values are null + Must be one of the following: + [ALERTRULE_STATE_DATA_OK, ALERTRULE_STATE_DATA_ALERTING, ALERTRULE_STATE_DATA_NODATA ] + :param errorAlertState: Alert state if execution error or timeout + Must be one of the following: + [ALERTRULE_STATE_DATA_OK, ALERTRULE_STATE_DATA_ALERTING, ALERTRULE_STATE_DATA_ERROR ] + + :param timeRangeFrom: Time range interpolation data start from + :param timeRangeTo: Time range interpolation data finish at + :param uid: Alert UID should be unique + :param dashboard_uid: Dashboard UID that should be use for linking on alert message + :param panel_id: Panel ID that should should be use for linking on alert message + """ + + title = attr.ib() + triggers = attr.ib(validator=is_valid_triggers) + annotations = attr.ib(default={}, validator=instance_of(dict)) + labels = attr.ib(default={}, validator=instance_of(dict)) + + evaluateInterval = attr.ib(default=DEFAULT_ALERT_EVALUATE_INTERVAL, validator=instance_of(str)) + evaluateFor = attr.ib(default=DEFAULT_ALERT_EVALUATE_FOR, validator=instance_of(str)) + noDataAlertState = attr.ib( + default=ALERTRULE_STATE_DATA_ALERTING, + validator=in_([ + ALERTRULE_STATE_DATA_OK, + ALERTRULE_STATE_DATA_ALERTING, + ALERTRULE_STATE_DATA_NODATA + ]) + ) + errorAlertState = attr.ib( + default=ALERTRULE_STATE_DATA_ALERTING, + validator=in_([ + ALERTRULE_STATE_DATA_OK, + ALERTRULE_STATE_DATA_ALERTING, + ALERTRULE_STATE_DATA_ERROR + ]) + ) + timeRangeFrom = attr.ib(default=300, validator=instance_of(int)) + timeRangeTo = attr.ib(default=0, validator=instance_of(int)) + uid = attr.ib(default=None, validator=attr.validators.optional(instance_of(str))) + dashboard_uid = attr.ib(default="", validator=instance_of(str)) + panel_id = attr.ib(default=0, validator=instance_of(int)) + + rule_group = attr.ib(default="") + + def to_json_data(self): + data = [] + conditions = [] + + for target, condition in self.triggers: + data += [{ + "refId": target.refId, + "relativeTimeRange": { + "from": self.timeRangeFrom, + "to": self.timeRangeTo + }, + "datasourceUid": target.datasource, + "model": target.to_json_data(), + }] + + # discard unused features of condition as of grafana 8.x + condition.useNewAlerts = True + + condition.target = target + conditions += [condition.to_json_data()] + + data += [{ + "refId": "CONDITION", + "datasourceUid": "-100", + "model": { + "conditions": conditions, + "refId": "CONDITION", + "type": "classic_conditions" + } + }] + + return { + "for": self.evaluateFor, + "labels": self.labels, + "annotations": self.annotations, + "grafana_alert": { + "title": self.title, + "condition": "CONDITION", + "data": data, + "intervalSeconds": self.evaluateInterval, + "exec_err_state": self.errorAlertState, + "no_data_state": self.noDataAlertState, + "uid": self.uid, + "rule_group": self.rule_group, + } + } + + @attr.s class Notification(object): diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 2b516010..0cc29beb 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -430,7 +430,7 @@ class ElasticsearchAlertCondition(AlertCondition): :param type: CTYPE_* """ - target = attr.ib(validator=instance_of(ElasticsearchTarget)) + target = attr.ib(default=None, validator=instance_of(ElasticsearchTarget)) @attr.s diff --git a/grafanalib/tests/examples/example.alertgroup.py b/grafanalib/tests/examples/example.alertgroup.py new file mode 100644 index 00000000..f6d963c3 --- /dev/null +++ b/grafanalib/tests/examples/example.alertgroup.py @@ -0,0 +1,93 @@ +"""Example grafana 8.x+ Alert""" + + +from grafanalib.core import ( + AlertGroup, + AlertRule, + Target, + AlertCondition, + LowerThan, + OP_OR, + OP_AND, + RTYPE_LAST +) + +# An AlertGroup is one group contained in an alert folder. +alertgroup = AlertGroup( + name="Production Alerts", + # Each AlertRule forms a separate alert. + rules=[ + AlertRule( + # Each rule must have a unique title + title="Database is unresponsive", + # Several triggers can be used per alert, a trigger is a combination of a Target and its AlertCondition in a tuple. + triggers=[ + ( + # A target refId must be assigned, and exist only once per AlertRule. + Target( + expr='sum(kube_pod_container_status_ready{exported_pod=~"database-/*"})', + datasource="VictoriaMetrics", + refId="A", + ), + AlertCondition( + evaluator=LowerThan(1), + # To have the alert fire when either of the triggers are met in the rule, set both AlertCondition operators to OP_OR. + operator=OP_OR, + reducerType=RTYPE_LAST + ) + ), + ( + Target( + expr='sum by (app) (count_over_time({app="database"}[5m]))', + datasource="Loki", + refId="B", + ), + AlertCondition( + evaluator=LowerThan(1000), + operator=OP_OR, + reducerType=RTYPE_LAST + ) + ) + ], + annotations={ + "summary": "The database is down", + "runbook_url": "https://runbook-for-this-scenario.com/foo", + }, + labels={ + "environment": "prod", + "slack": "prod-alerts", + }, + evaluateInterval="1m", + evaluateFor="3m", + ), + + # Second alert + AlertRule( + title="Service API blackbox failure", + triggers=[ + ( + Target( + expr='probe_success{instance="https://my-service.foo.com/ready"}', + datasource="VictoriaMetrics", + refId="A", + ), + AlertCondition( + evaluator=LowerThan(1), + operator=OP_AND, + reducerType=RTYPE_LAST, + ) + ) + ], + annotations={ + "summary": "Service API has been unavailable for 3 minutes", + "runbook_url": "https://runbook-for-this-scenario.com/foo", + }, + labels={ + "environment": "prod", + "slack": "prod-alerts", + }, + evaluateInterval="1m", + evaluateFor="3m", + ) + ] +) diff --git a/grafanalib/tests/examples/example.upload-alerts.py b/grafanalib/tests/examples/example.upload-alerts.py new file mode 100644 index 00000000..79081f01 --- /dev/null +++ b/grafanalib/tests/examples/example.upload-alerts.py @@ -0,0 +1,61 @@ +from grafanalib.core import AlertGroup +from grafanalib._gen import DashboardEncoder, loader +import json +import requests +from os import getenv + + +def get_alert_json(alert: AlertGroup): + ''' + get_alert_json generates JSON from grafanalib AlertGroup object + + :param alert - AlertGroup created via grafanalib + ''' + + return json.dumps(alert.to_json_data(), sort_keys=True, indent=4, cls=DashboardEncoder) + + +def upload_to_grafana(alertjson, folder, server, api_key, session_cookie, verify=True): + ''' + upload_to_grafana tries to upload the AlertGroup to grafana and prints response + WARNING: This will first delete all alerts in the AlertGroup before replacing them with the provided AlertGroup. + + :param alertjson - AlertGroup json generated by grafanalib + :param folder - Folder to upload the AlertGroup into + :param server - grafana server name + :param api_key - grafana api key with read and write privileges + ''' + groupName = json.loads(alertjson)['name'] + + headers = {} + if api_key: + print("using bearer auth") + headers['Authorization'] = f"Bearer {api_key}" + + if session_cookie: + print("using session cookie") + headers['Cookie'] = session_cookie + + print(f"deleting AlertGroup {groupName} in folder {folder}") + r = requests.delete(f"https://{server}/api/ruler/grafana/api/v1/rules/{folder}/{groupName}", headers=headers, verify=verify) + print(f"{r.status_code} - {r.content}") + + headers['Content-Type'] = 'application/json' + + print(f"ensuring folder {folder} exists") + r = requests.post(f"https://{server}/api/folders", data={"title": folder}, headers=headers) + print(f"{r.status_code} - {r.content}") + + print(f"uploading AlertGroup {groupName} to folder {folder}") + r = requests.post(f"https://{server}/api/ruler/grafana/api/v1/rules/{folder}", data=alertjson, headers=headers, verify=verify) + # TODO: add error handling + print(f"{r.status_code} - {r.content}") + + +grafana_api_key = getenv("GRAFANA_API_KEY") +grafana_server = getenv("GRAFANA_SERVER") +grafana_cookie = getenv("GRAFANA_COOKIE") + +# Generate an alert from the example +my_alergroup_json = get_alert_json(loader("./grafanalib/tests/examples/example.alertgroup.py")) +upload_to_grafana(my_alergroup_json, "testfolder", grafana_server, grafana_api_key, grafana_cookie) diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index c261a65f..61a4d8f4 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -751,6 +751,122 @@ def test_alert(): alert.to_json_data() +def test_alertgroup(): + name = "Example Alert Group" + group = G.AlertGroup( + name=name, + rules=[ + G.AlertRule( + title="My Important Alert!", + triggers=[ + ( + G.Target(refId="A"), + G.AlertCondition( + evaluator=G.LowerThan(1), + operator=G.OP_OR, + ), + ), + ( + G.Target(refId="B"), + G.AlertCondition( + evaluator=G.GreaterThan(1), + operator=G.OP_OR, + ) + ) + ] + ) + ] + ) + + output = group.to_json_data() + + assert output["name"] == name + assert output["rules"][0]["grafana_alert"]["rule_group"] == name + + +def test_alertrule(): + title = "My Important Alert!" + annotations = {"summary": "this alert fires when prod is down!!!"} + labels = {"severity": "serious"} + rule = G.AlertRule( + title=title, + triggers=[ + ( + G.Target( + refId="A", + datasource="Prometheus", + ), + G.AlertCondition( + evaluator=G.LowerThan(1), + operator=G.OP_OR, + ), + ), + ( + G.Target( + refId="B", + datasource="Prometheus", + ), + G.AlertCondition( + evaluator=G.GreaterThan(1), + operator=G.OP_OR, + ) + ) + ], + annotations=annotations, + labels=labels, + evaluateFor="3m", + ) + + data = rule.to_json_data() + assert data['grafana_alert']['title'] == title + assert data['annotations'] == annotations + assert data['labels'] == labels + assert data['for'] == "3m" + + +def test_alertrule_invalid_triggers(): + # test that triggers is a list of [(Target, AlertCondition)] + + with pytest.raises(ValueError): + G.AlertRule( + title="Invalid rule", + triggers=[ + G.Target( + refId="A", + datasource="Prometheus", + ), + ], + ) + + with pytest.raises(ValueError): + G.AlertRule( + title="Invalid rule", + triggers=[ + ( + "foo", + G.AlertCondition( + evaluator=G.GreaterThan(1), + operator=G.OP_OR, + ) + ), + ], + ) + + with pytest.raises(ValueError): + G.AlertRule( + title="Invalid rule", + triggers=[ + ( + G.Target( + refId="A", + datasource="Prometheus", + ), + "bar" + ), + ], + ) + + def test_worldmap(): data_source = 'dummy data source' targets = ['dummy_prom_query'] diff --git a/grafanalib/tests/test_examples.py b/grafanalib/tests/test_examples.py index d23f6bd4..baf1f73b 100644 --- a/grafanalib/tests/test_examples.py +++ b/grafanalib/tests/test_examples.py @@ -11,13 +11,25 @@ def test_examples(): '''Run examples in ./examples directory.''' + # Run dashboard examples examples_dir = os.path.join(os.path.dirname(__file__), 'examples') - examples = glob.glob('{}/*.dashboard.py'.format(examples_dir)) - assert len(examples) == 2 + dashboards = glob.glob('{}/*.dashboard.py'.format(examples_dir)) + assert len(dashboards) == 2 stdout = io.StringIO() - for example in examples: + for example in dashboards: with redirect_stdout(stdout): ret = _gen.generate_dashboard([example]) assert ret == 0 assert stdout.getvalue() != '' + + # Run alertgroup example + alerts = glob.glob('{}/*.alertgroup.py'.format(examples_dir)) + assert len(alerts) == 1 + + stdout = io.StringIO() + for example in alerts: + with redirect_stdout(stdout): + ret = _gen.generate_alertgroup([example]) + assert ret == 0 + assert stdout.getvalue() != '' diff --git a/setup.py b/setup.py index 45a9fbb5..e7cc0ce2 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see - # https://packaging.python.org/en/latest/single_source_version.html + # https://packaging.python.org/en/latest/guides/single-sourcing-package-version/ version='0.6.3', description='Library for building Grafana dashboards', long_description=open(README).read(), @@ -52,6 +52,8 @@ def local_file(name): 'console_scripts': [ 'generate-dashboard=grafanalib._gen:generate_dashboard_script', 'generate-dashboards=grafanalib._gen:generate_dashboards_script', + 'generate-alertgroup=grafanalib._gen:generate_alertgroup_script', + 'generate-alertgroups=grafanalib._gen:generate_alertgroups_script' ], }, ) From 633f0a3d00fc13ac5acc98019113d2c870edc3c6 Mon Sep 17 00:00:00 2001 From: Oscar van Leusen Date: Fri, 14 Oct 2022 16:00:16 +0100 Subject: [PATCH 346/403] Improve formatting of getting started document (#534) I thought this would format like markdown, but it didn't so it looked awful. --- docs/getting-started.rst | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index cace4a4d..7950e8a8 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -61,17 +61,21 @@ The following will configure a couple of alerts inside a group. Although this example has a fair amount of boilerplate, when creating large numbers of similar alerts it can save lots of time to programatically fill these fields. -Each `AlertGroup` represents a folder within Grafana's alerts tab. This consists -of one or more `AlertRule`, which contains one or more triggers. Triggers define +Each ``AlertGroup`` represents a folder within Grafana's alerts tab. This consists +of one or more ``AlertRule``, which contains one or more triggers. Triggers define what will cause the alert to fire. -A trigger is made up of a `Target` (a Grafana query on a datasource) and an +A trigger is made up of a ``Target`` (a Grafana query on a datasource) and an `AlertCondition` (a condition this query must satisfy in order to alert). Finally, there are additional settings like: -* How the alert will behave when data sources have problems (`noDataAlertState` and `errorAlertState`) -* How frequently the trigger is evaluated (`evaluateInterval`) -* How long the AlertCondition needs to be met before the alert fires (`evaluateFor`) + +* How the alert will behave when data sources have problems (``noDataAlertState`` and ``errorAlertState``) + +* How frequently the trigger is evaluated (``evaluateInterval``) + +* How long the AlertCondition needs to be met before the alert fires (``evaluateFor``) + * Annotations and labels, which help provide contextual information and direct where your alerts will go From b547a0bc879cf46fbd6bd67b66115509c0dda513 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 14:18:50 +0100 Subject: [PATCH 347/403] Bump sphinx from 5.2.3 to 5.3.0 (#536) Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.2.3 to 5.3.0. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.2.3...v5.3.0) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 81c9b5e9..cf133465 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 5.2.3 +sphinx == 5.3.0 sphinx_rtd_theme == 1.0.0 \ No newline at end of file From c9a77d481da8d76a91ad92efb1a155d6dae8a34d Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Wed, 2 Nov 2022 09:56:07 +0000 Subject: [PATCH 348/403] Add support for Grafana alerts in v9 (#535) * Added Grafana 8.x new Alert Rule * [ADD] test for AlertRuler [ADD] Doc String on AlertRuler [UPD] Annotations Management [ADD] Default value * [FIX] test on AlertRuler * [UPD] uid on AlertRuler optional * [FIX] Test on AlertRuler * [FIX] Prettify code around the test * [FIX] Unit test of Alert Ruler Class * Finish AlertRule type and add AlertGroup type for Grafana 8.x alerts. Add unit tests, example usage, example upload script, and console generate-alertgroup(s) commands * Fix failing flake8 * Add class to add v9 alerts and file based provisioning * Fix issue where target refId not set * Add missing alert fields * Fix tests * Add tests for new alert classes * Update changelog * Add examples of alerting v9 * Remove broken links * Only run build on main branch * Revert - Only run build on main branch * Only run test on push tp main branch * test-update runner to 22.04 * test-update runner to 20.04 to support py36 * Add validation of alert v9 inputs * Add alert group interval config and alert state * Update example alerts * Document alerting rule generation * fix missing link error for example links * fix missing link error for example links * Update .github/workflows/run-tests.yml Co-authored-by: Daniel Holbach * Remove support for Python 3.6 * Add comment to example alerts to set datasource name * Fix typo in alert command * Fix link to readthedocs Co-authored-by: Vincent CANDEAU Co-authored-by: Oscar van Leusen Co-authored-by: Nikita Lozhnikov Co-authored-by: Daniel Holbach --- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 9 +- CHANGELOG.rst | 1 + docs/getting-started.rst | 99 ++++++- grafanalib/core.py | 280 +++++++++++++++++- ...roup.py => example.alertsv8.alertgroup.py} | 15 +- ...ple.alertsv9.alertfilebasedprovisioning.py | 103 +++++++ .../examples/example.alertsv9.alertgroup.py | 100 +++++++ grafanalib/tests/test_core.py | 99 ++++++- grafanalib/tests/test_examples.py | 11 + 10 files changed, 685 insertions(+), 34 deletions(-) rename grafanalib/tests/examples/{example.alertgroup.py => example.alertsv8.alertgroup.py} (85%) create mode 100644 grafanalib/tests/examples/example.alertsv9.alertfilebasedprovisioning.py create mode 100644 grafanalib/tests/examples/example.alertsv9.alertgroup.py diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index d66bb0c0..2c0eb315 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -5,7 +5,7 @@ on: push jobs: build-n-publish: name: Build and publish Python 🐍 distributions 📦 to PyPI - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - name: Set up Python 3.7 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6db9f740..6d3fe23a 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,14 +1,17 @@ name: Run tests -on: [push, pull_request] +on: + push: + branches: main + pull_request: jobs: build-n-publish: name: Run tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-20.04 strategy: matrix: - python: ['3.6', '3.7', '3.8', '3.9', '3.10'] + python: ['3.7', '3.8', '3.9', '3.10'] steps: - uses: actions/checkout@v3 - name: Set up Python diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 035a07aa..3f9dc996 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,7 @@ x.x.x (TBC) * Added ... * Added Grafana 8.x new Alert Rule +* Added Grafana 9.x new Alert Rule * Added ePict_ plugin. * Added ae3e plotly panel support * Added datasource parameter to Influxdb targets diff --git a/docs/getting-started.rst b/docs/getting-started.rst index 7950e8a8..d817f85e 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -12,7 +12,7 @@ Grafana migrates dashboards to the latest Grafana schema version on import, meaning that dashboards created with grafanalib are supported by all versions of Grafana. You may find that some of the latest features are missing from grafanalib, please refer to the `module documentation -`_ for information +`_ for information about supported features. If you find a missing feature please raise an issue or submit a PR to the GitHub `repository `_ @@ -50,23 +50,34 @@ code. The following example provides minimal code boilerplate for it: .. literalinclude:: ../grafanalib/tests/examples/example.upload-dashboard.py :language: python +Alternatively Grafana supports file based provisioning, where dashboard files +are periodically loaded into the Grafana database. Tools like Anisble can +assist with the deployment. + Writing Alerts -================== +============== + +Between Grafana versions there have been significant changes in how alerts +are managed. Bellow are some example of how to configure alerting in +Grafana v8 and Grafana v9. + +Alerts in Grafana v8 +-------------------- The following will configure a couple of alerts inside a group. -.. literalinclude:: ../grafanalib/tests/examples/example.alertgroup.py +.. literalinclude:: ../grafanalib/tests/examples/example.alertsv8.alertgroup.py :language: python Although this example has a fair amount of boilerplate, when creating large numbers -of similar alerts it can save lots of time to programatically fill these fields. +of similar alerts it can save lots of time to programmatically fill these fields. Each ``AlertGroup`` represents a folder within Grafana's alerts tab. This consists -of one or more ``AlertRule``, which contains one or more triggers. Triggers define +of one or more ``AlertRulev8``, which contains one or more triggers. Triggers define what will cause the alert to fire. A trigger is made up of a ``Target`` (a Grafana query on a datasource) and an -`AlertCondition` (a condition this query must satisfy in order to alert). +``AlertCondition`` (a condition this query must satisfy in order to alert). Finally, there are additional settings like: @@ -79,27 +90,89 @@ Finally, there are additional settings like: * Annotations and labels, which help provide contextual information and direct where your alerts will go +Alerts in Grafana v9 +-------------------- + +The following will configure a couple of alerts inside a group for Grafana v9.x+. + +.. literalinclude:: ../grafanalib/tests/examples/example.alertsv9.alertgroup.py + :language: python + +Although this example has a fair amount of boilerplate, when creating large numbers +of similar alerts it can save lots of time to programmatically fill these fields. + +Each ``AlertGroup`` represents a folder within Grafana's alerts tab. This consists +of one or more ``AlertRulev9``, which contains a list of triggers, that define what +will cause the alert to fire. + +A trigger can either be a ``Target`` (a Grafana query on a datasource) or an +``AlertExpression`` (a expression performed on one of the triggers). + +An ``AlertExpression`` can be one of 4 types + +* Classic - Contains and list of ``AlertCondition``'s that are evaluated +* Reduce - Reduce the queried data +* Resample - Resample the queried data +* Math - Expression with the condition for the rule + +Finally, there are additional settings like: + +* How the alert will behave when data sources have problems (``noDataAlertState`` and ``errorAlertState``) + +* How frequently the each rule in the Alert Group is evaluated (``evaluateInterval``) + +* How long the AlertCondition needs to be met before the alert fires (``evaluateFor``) + +* Annotations and labels, which help provide contextual information and direct where + your alerts will go + + Generating Alerts -===================== +================= -If you save the above as ``example.alertgroup.py`` (the suffix must be -``.alertgroup.py``), you can then generate the JSON dashboard with: +If you save either of the above examples for Grafana v8 or v9 as ``example.alertgroup.py`` +(the suffix must be ``.alertgroup.py``), you can then generate the JSON alert with: .. code-block:: console - $ generate-alertgroups -o alerts.json example.alertgroup.py + $ generate-alertgroup -o alerts.json example.alertgroup.py Uploading alerts from code -=============================== +========================== As Grafana does not currently have a user interface for importing alertgroup JSON, -you must upload the alerts via Grafana's REST API. +you must either upload the alerts via Grafana's REST API or use file based provisioning. + +Uploading alerts from code using REST API +----------------------------------------- The following example provides minimal code boilerplate for it: .. literalinclude:: ../grafanalib/tests/examples/example.upload-alerts.py :language: python +Uploading alerts from code using File Based Provisioning +-------------------------------------------------------- + +The alternative to using Grafana's REST API is to use its file based provisioning for +alerting. + +The following example uses the ``AlertFileBasedProvisioning`` class to provision a list +of alert groups: + +.. literalinclude:: ../grafanalib/tests/examples/example.alertsv9.alertfilebasedprovisioning.py + :language: python + +Save the above example as ``example.alertfilebasedprovisioning.py`` +(the suffix must be ``.alertfilebasedprovisioning.py``), you can then generate the JSON alert with: + +.. code-block:: console + + $ generate-alertgroup -o alerts.json example.alertfilebasedprovisioning.py + +Then place the file in the ``provisioning/alerting`` directory and start Grafana +Tools like Anisble can assist with the deployment of the alert file. + Installation ============ @@ -115,7 +188,7 @@ Support This library is in its very early stages. We'll probably make changes that break backwards compatibility, although we'll try hard not to. -grafanalib works with Python 3.6, 3.7, 3.8 and 3.9. +grafanalib works with Python 3.7, 3.8, 3.9 and 3.10. Developing ========== diff --git a/grafanalib/core.py b/grafanalib/core.py index f8e7d0ce..5430d6d5 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -187,6 +187,26 @@ def to_json_data(self): OP_AND = 'and' OP_OR = 'or' +# Alert Expression Types +# classic/reduce/resample/math +EXP_TYPE_CLASSIC = 'classic_conditions' +EXP_TYPE_REDUCE = 'reduce' +EXP_TYPE_RESAMPLE = 'resample' +EXP_TYPE_MATH = 'math' + +# Alert Expression Reducer Function +EXP_REDUCER_FUNC_MIN = 'min' +EXP_REDUCER_FUNC_MAX = 'max' +EXP_REDUCER_FUNC_MEAN = 'mean' +EXP_REDUCER_FUNC_SUM = 'sum' +EXP_REDUCER_FUNC_COUNT = 'count' +EXP_REDUCER_FUNC_LAST = 'last' + +# Alert Expression Reducer Mode +EXP_REDUCER_MODE_STRICT = 'strict' +EXP_REDUCER_FUNC_DROP_NN = 'dropNN' +EXP_REDUCER_FUNC_REPLACE_NN = 'replaceNN' + # Text panel modes TEXT_MODE_MARKDOWN = 'markdown' TEXT_MODE_HTML = 'html' @@ -565,6 +585,7 @@ class Target(object): def to_json_data(self): return { 'expr': self.expr, + 'query': self.expr, 'target': self.target, 'format': self.format, 'hide': self.hide, @@ -1197,6 +1218,135 @@ def to_json_data(self): return condition +@attr.s +class AlertExpression(object): + """ + A alert expression to be evaluated in Grafana v9.x+ + + :param refId: Expression reference ID (A,B,C,D,...) + :param expression: Input reference ID (A,B,C,D,...) for expression to evaluate, or in the case of the Math type the expression to evaluate + :param conditions: list of AlertConditions + :param expressionType: Expression type EXP_TYPE_* + Supported expression types: + EXP_TYPE_CLASSIC + EXP_TYPE_REDUCE + EXP_TYPE_RESAMPLE + EXP_TYPE_MATH + :param hide: Hide alert boolean + :param intervalMs: Expression evaluation interval + :param maxDataPoints: Maximum number fo data points to be evaluated + + :param reduceFunction: Reducer function (Only used if expressionType=EXP_TYPE_REDUCE) + Supported reducer functions: + EXP_REDUCER_FUNC_MIN + EXP_REDUCER_FUNC_MAX + EXP_REDUCER_FUNC_MEAN + EXP_REDUCER_FUNC_SUM + EXP_REDUCER_FUNC_COUNT + EXP_REDUCER_FUNC_LAST + :param reduceMode: Reducer mode (Only used if expressionType=EXP_TYPE_REDUCE) + Supported reducer modes: + EXP_REDUCER_MODE_STRICT + EXP_REDUCER_FUNC_DROP_NN + EXP_REDUCER_FUNC_REPLACE_NN + :param reduceReplaceWith: When using mode EXP_REDUCER_FUNC_REPLACE_NN number that will replace non numeric values + + :param resampleWindow: Intervale to resample to eg. 10s, 1m, 30m, 1h + :param resampleDownsampler: 'mean', 'min', 'max', 'sum' + :param resampleUpsampler: + 'fillna' - Fill with NaN's + 'pad' - fill with the last known value + 'backfilling' - fill with the next know value + """ + + refId = attr.ib() + expression = attr.ib(validator=instance_of(str)) + conditions = attr.ib(default=attr.Factory(list), validator=attr.validators.deep_iterable( + member_validator=instance_of(AlertCondition), + iterable_validator=instance_of(list) + )) + expressionType = attr.ib( + default=EXP_TYPE_CLASSIC, + validator=in_([ + EXP_TYPE_CLASSIC, + EXP_TYPE_REDUCE, + EXP_TYPE_RESAMPLE, + EXP_TYPE_MATH + ]) + ) + hide = attr.ib(default=False, validator=instance_of(bool)) + intervalMs = attr.ib(default=1000, validator=instance_of(int)) + maxDataPoints = attr.ib(default=43200, validator=instance_of(int)) + + reduceFunction = attr.ib( + default=EXP_REDUCER_FUNC_MEAN, + validator=in_([ + EXP_REDUCER_FUNC_MIN, + EXP_REDUCER_FUNC_MAX, + EXP_REDUCER_FUNC_MEAN, + EXP_REDUCER_FUNC_SUM, + EXP_REDUCER_FUNC_COUNT, + EXP_REDUCER_FUNC_LAST + ]) + ) + reduceMode = attr.ib( + default=EXP_REDUCER_MODE_STRICT, + validator=in_([ + EXP_REDUCER_MODE_STRICT, + EXP_REDUCER_FUNC_DROP_NN, + EXP_REDUCER_FUNC_REPLACE_NN + ]) + ) + reduceReplaceWith = attr.ib(default=0) + + resampleWindow = attr.ib(default='10s', validator=instance_of(str)) + resampleDownsampler = attr.ib(default='mean') + resampleUpsampler = attr.ib(default='fillna') + + def to_json_data(self): + + conditions = [] + + for condition in self.conditions: + # discard unused features of condition as of grafana 8.x + condition.useNewAlerts = True + condition.target = Target(refId=self.expression) + conditions += [condition.to_json_data()] + + expression = { + 'refId': self.refId, + 'queryType': '', + 'relativeTimeRange': { + 'from': 0, + 'to': 0 + }, + 'datasourceUid': '-100', + 'model': { + 'conditions': conditions, + 'datasource': { + 'type': '__expr__', + 'uid': '-100' + }, + 'expression': self.expression, + 'hide': self.hide, + 'intervalMs': self.intervalMs, + 'maxDataPoints': self.maxDataPoints, + 'refId': self.refId, + 'type': self.expressionType, + 'reducer': self.reduceFunction, + 'settings': { + 'mode': self.reduceMode, + 'replaceWithValue': self.reduceReplaceWith + }, + 'downsampler': self.resampleDownsampler, + 'upsampler': self.resampleUpsampler, + 'window': self.resampleWindow + } + } + + return expression + + @attr.s class Alert(object): """ @@ -1242,9 +1392,13 @@ class AlertGroup(object): :param name: Alert group name :param rules: List of AlertRule + :param folder: Folder to hold alert (Grafana 9.x) + :param evaluateInterval: Interval at which the group of alerts is to be evaluated """ name = attr.ib() rules = attr.ib(default=attr.Factory(list), validator=instance_of(list)) + folder = attr.ib(default='alert', validator=instance_of(str)) + evaluateInterval = attr.ib(default='1m', validator=instance_of(str)) def group_rules(self, rules): grouped_rules = [] @@ -1256,8 +1410,9 @@ def group_rules(self, rules): def to_json_data(self): return { 'name': self.name, - 'interval': "1m", + 'interval': self.evaluateInterval, 'rules': self.group_rules(self.rules), + 'folder': self.folder } @@ -1273,10 +1428,20 @@ def is_valid_triggers(instance, attribute, value): is_valid_target(instance, "alert trigger target", trigger[0]) +def is_valid_triggersv9(instance, attribute, value): + """Validator for AlertRule triggers for Grafana v9""" + for trigger in value: + if not (isinstance(trigger, Target) or isinstance(trigger, AlertExpression)): + raise ValueError(f"{attribute.name} must either be a Target or AlertCondition") + + if isinstance(trigger, Target): + is_valid_target(instance, "alert trigger target", trigger) + + @attr.s -class AlertRule(object): +class AlertRulev8(object): """ - Create a Grafana 8.x+ Alert Rule + Create a Grafana 8.x Alert Rule :param title: The alert's title, must be unique per folder :param triggers: A list of Target and AlertCondition tuples, [(Target, AlertCondition)]. @@ -1382,6 +1547,113 @@ def to_json_data(self): } +@attr.s +class AlertRulev9(object): + """ + Create a Grafana 9.x+ Alert Rule + + :param title: The alert's title, must be unique per folder + :param triggers: A list of Targets and AlertConditions. + The Target specifies the query, and the AlertCondition specifies how this is used to alert. + :param annotations: Summary and annotations + Dictionary with one of the following key or custom key + ['runbook_url', 'summary', 'description', '__alertId__', '__dashboardUid__', '__panelId__'] + :param labels: Custom Labels for the metric, used to handle notifications + :param condition: Set one of the queries or expressions as the alert condition by refID (Grafana 9.x) + + :param evaluateFor: The duration for which the condition must be true before an alert fires + The Interval is set by the alert group + :param noDataAlertState: Alert state if no data or all values are null + Must be one of the following: + [ALERTRULE_STATE_DATA_OK, ALERTRULE_STATE_DATA_ALERTING, ALERTRULE_STATE_DATA_NODATA ] + :param errorAlertState: Alert state if execution error or timeout + Must be one of the following: + [ALERTRULE_STATE_DATA_OK, ALERTRULE_STATE_DATA_ALERTING, ALERTRULE_STATE_DATA_ERROR ] + + :param timeRangeFrom: Time range interpolation data start from + :param timeRangeTo: Time range interpolation data finish at + :param uid: Alert UID should be unique + :param dashboard_uid: Dashboard UID that should be use for linking on alert message + :param panel_id: Panel ID that should should be use for linking on alert message + """ + + title = attr.ib() + triggers = attr.ib(default=[], validator=is_valid_triggersv9) + annotations = attr.ib(default={}, validator=instance_of(dict)) + labels = attr.ib(default={}, validator=instance_of(dict)) + + evaluateFor = attr.ib(default=DEFAULT_ALERT_EVALUATE_FOR, validator=instance_of(str)) + noDataAlertState = attr.ib( + default=ALERTRULE_STATE_DATA_ALERTING, + validator=in_([ + ALERTRULE_STATE_DATA_OK, + ALERTRULE_STATE_DATA_ALERTING, + ALERTRULE_STATE_DATA_NODATA + ]) + ) + errorAlertState = attr.ib( + default=ALERTRULE_STATE_DATA_ALERTING, + validator=in_([ + ALERTRULE_STATE_DATA_OK, + ALERTRULE_STATE_DATA_ALERTING, + ALERTRULE_STATE_DATA_ERROR + ]) + ) + condition = attr.ib(default='B') + timeRangeFrom = attr.ib(default=300, validator=instance_of(int)) + timeRangeTo = attr.ib(default=0, validator=instance_of(int)) + uid = attr.ib(default=None, validator=attr.validators.optional(instance_of(str))) + dashboard_uid = attr.ib(default="", validator=instance_of(str)) + panel_id = attr.ib(default=0, validator=instance_of(int)) + + def to_json_data(self): + data = [] + + for trigger in self.triggers: + if isinstance(trigger, Target): + target = trigger + data += [{ + "refId": target.refId, + "relativeTimeRange": { + "from": self.timeRangeFrom, + "to": self.timeRangeTo + }, + "datasourceUid": target.datasource, + "model": target.to_json_data(), + }] + else: + data += [trigger.to_json_data()] + + return { + "title": self.title, + "uid": self.uid, + "condition": self.condition, + "for": self.evaluateFor, + "labels": self.labels, + "annotations": self.annotations, + "data": data, + "noDataState": self.noDataAlertState, + "execErrState": self.errorAlertState + } + + +@attr.s +class AlertFileBasedProvisioning(object): + """ + Used to generate JSON data valid for file based alert provisioning + + param alertGroup: List of AlertGroups + """ + + groups = attr.ib() + + def to_json_data(self): + return { + 'apiVersion': 1, + 'groups': self.groups, + } + + @attr.s class Notification(object): @@ -1902,7 +2174,6 @@ class TimeSeries(Panel): :param legendCalcs: which calculations should be displayed in the legend. Defaults to an empty list. Possible values are: allIsNull, allIsZero, changeCount, count, delta, diff, diffperc, distinctCount, firstNotNull, max, mean, min, logmin, range, step, total. For more information see - https://grafana.com/docs/grafana/next/panels/calculation-types/ :param lineInterpolation: line interpolation linear (Default), smooth, stepBefore, stepAfter :param lineWidth: line width, default 1 @@ -3946,7 +4217,6 @@ def to_json_data(self): @attr.s class News(Panel): """Generates News panel json structure - Grafana docs on State Timeline panel: https://grafana.com/docs/grafana/next/visualizations/news-panel/ :param feedUrl: URL to query, only RSS feed formats are supported (not Atom). :param showImage: Controls if the news item social (og:image) image is shown above text content diff --git a/grafanalib/tests/examples/example.alertgroup.py b/grafanalib/tests/examples/example.alertsv8.alertgroup.py similarity index 85% rename from grafanalib/tests/examples/example.alertgroup.py rename to grafanalib/tests/examples/example.alertsv8.alertgroup.py index f6d963c3..dd944872 100644 --- a/grafanalib/tests/examples/example.alertgroup.py +++ b/grafanalib/tests/examples/example.alertsv8.alertgroup.py @@ -3,7 +3,7 @@ from grafanalib.core import ( AlertGroup, - AlertRule, + AlertRulev8, Target, AlertCondition, LowerThan, @@ -17,7 +17,7 @@ name="Production Alerts", # Each AlertRule forms a separate alert. rules=[ - AlertRule( + AlertRulev8( # Each rule must have a unique title title="Database is unresponsive", # Several triggers can be used per alert, a trigger is a combination of a Target and its AlertCondition in a tuple. @@ -26,6 +26,7 @@ # A target refId must be assigned, and exist only once per AlertRule. Target( expr='sum(kube_pod_container_status_ready{exported_pod=~"database-/*"})', + # Set datasource to name of your datasource datasource="VictoriaMetrics", refId="A", ), @@ -39,6 +40,7 @@ ( Target( expr='sum by (app) (count_over_time({app="database"}[5m]))', + # Set datasource to name of your datasource datasource="Loki", refId="B", ), @@ -51,7 +53,7 @@ ], annotations={ "summary": "The database is down", - "runbook_url": "https://runbook-for-this-scenario.com/foo", + "runbook_url": "runbook-for-this-scenario.com/foo", }, labels={ "environment": "prod", @@ -62,12 +64,13 @@ ), # Second alert - AlertRule( + AlertRulev8( title="Service API blackbox failure", triggers=[ ( Target( - expr='probe_success{instance="https://my-service.foo.com/ready"}', + expr='probe_success{instance="my-service.foo.com/ready"}', + # Set datasource to name of your datasource datasource="VictoriaMetrics", refId="A", ), @@ -80,7 +83,7 @@ ], annotations={ "summary": "Service API has been unavailable for 3 minutes", - "runbook_url": "https://runbook-for-this-scenario.com/foo", + "runbook_url": "runbook-for-this-scenario.com/foo", }, labels={ "environment": "prod", diff --git a/grafanalib/tests/examples/example.alertsv9.alertfilebasedprovisioning.py b/grafanalib/tests/examples/example.alertsv9.alertfilebasedprovisioning.py new file mode 100644 index 00000000..5644e41c --- /dev/null +++ b/grafanalib/tests/examples/example.alertsv9.alertfilebasedprovisioning.py @@ -0,0 +1,103 @@ +"""Example grafana 9.x+ Alert""" + + +from grafanalib.core import ( + AlertGroup, + AlertRulev9, + Target, + AlertCondition, + AlertExpression, + AlertFileBasedProvisioning, + GreaterThan, + OP_AND, + RTYPE_LAST, + EXP_TYPE_CLASSIC, + EXP_TYPE_REDUCE, + EXP_TYPE_MATH +) + +# An AlertGroup is one group contained in an alert folder. +alertgroup = AlertGroup( + name="Production Alerts", + # Each AlertRule forms a separate alert. + rules=[ + # Alert rule using classic condition > 3 + AlertRulev9( + # Each rule must have a unique title + title="Alert for something 3", + uid='alert3', + # Several triggers can be used per alert + condition='B', + triggers=[ + # A target refId must be assigned, and exist only once per AlertRule. + Target( + expr="from(bucket: \"sensors\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"remote_cpu\")\n |> filter(fn: (r) => r[\"_field\"] == \"usage_system\")\n |> filter(fn: (r) => r[\"cpu\"] == \"cpu-total\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", + # Set datasource to name of your datasource + datasource="influxdb", + refId="A", + ), + AlertExpression( + refId="B", + expressionType=EXP_TYPE_CLASSIC, + expression='A', + conditions=[ + AlertCondition( + evaluator=GreaterThan(3), + operator=OP_AND, + reducerType=RTYPE_LAST + ) + ] + ) + ], + annotations={ + "summary": "The database is down", + "runbook_url": "runbook-for-this-scenario.com/foo", + }, + labels={ + "environment": "prod", + "slack": "prod-alerts", + }, + evaluateFor="3m", + ), + # Alert rule using reduce and Math + AlertRulev9( + # Each rule must have a unique title + title="Alert for something 4", + uid='alert4', + condition='C', + # Several triggers can be used per alert + triggers=[ + # A target refId must be assigned, and exist only once per AlertRule. + Target( + expr="from(bucket: \"sensors\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"remote_cpu\")\n |> filter(fn: (r) => r[\"_field\"] == \"usage_system\")\n |> filter(fn: (r) => r[\"cpu\"] == \"cpu-total\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", + # Set datasource to name of your datasource + datasource="influxdb", + refId="A", + ), + AlertExpression( + refId="B", + expressionType=EXP_TYPE_REDUCE, + expression='A', + reduceFunction='mean', + reduceMode='dropNN' + ), + AlertExpression( + refId="C", + expressionType=EXP_TYPE_MATH, + expression='$B < 3' + ) + ], + annotations={ + "summary": "The database is down", + "runbook_url": "runbook-for-this-scenario.com/foo", + }, + labels={ + "environment": "prod", + "slack": "prod-alerts", + }, + evaluateFor="3m", + ) + ] +) + +alertfilebasedprovisioning = AlertFileBasedProvisioning([alertgroup]) diff --git a/grafanalib/tests/examples/example.alertsv9.alertgroup.py b/grafanalib/tests/examples/example.alertsv9.alertgroup.py new file mode 100644 index 00000000..383471f9 --- /dev/null +++ b/grafanalib/tests/examples/example.alertsv9.alertgroup.py @@ -0,0 +1,100 @@ +"""Example grafana 9.x+ Alert""" + + +from grafanalib.core import ( + AlertGroup, + AlertRulev9, + Target, + AlertCondition, + AlertExpression, + GreaterThan, + OP_AND, + RTYPE_LAST, + EXP_TYPE_CLASSIC, + EXP_TYPE_REDUCE, + EXP_TYPE_MATH +) + +# An AlertGroup is one group contained in an alert folder. +alertgroup = AlertGroup( + name="Production Alerts", + # Each AlertRule forms a separate alert. + rules=[ + # Alert rule using classic condition > 3 + AlertRulev9( + # Each rule must have a unique title + title="Alert for something 1", + uid='alert1', + # Several triggers can be used per alert + condition='B', + triggers=[ + # A target refId must be assigned, and exist only once per AlertRule. + Target( + expr="from(bucket: \"sensors\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"remote_cpu\")\n |> filter(fn: (r) => r[\"_field\"] == \"usage_system\")\n |> filter(fn: (r) => r[\"cpu\"] == \"cpu-total\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", + # Set datasource to name of your datasource + datasource="influxdb", + refId="A", + ), + AlertExpression( + refId="B", + expressionType=EXP_TYPE_CLASSIC, + expression='A', + conditions=[ + AlertCondition( + evaluator=GreaterThan(3), + operator=OP_AND, + reducerType=RTYPE_LAST + ) + ] + ) + ], + annotations={ + "summary": "The database is down", + "runbook_url": "runbook-for-this-scenario.com/foo", + }, + labels={ + "environment": "prod", + "slack": "prod-alerts", + }, + evaluateFor="3m", + ), + # Alert rule using reduce and Math + AlertRulev9( + # Each rule must have a unique title + title="Alert for something 2", + uid='alert2', + condition='C', + # Several triggers can be used per alert + triggers=[ + # A target refId must be assigned, and exist only once per AlertRule. + Target( + expr="from(bucket: \"sensors\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"remote_cpu\")\n |> filter(fn: (r) => r[\"_field\"] == \"usage_system\")\n |> filter(fn: (r) => r[\"cpu\"] == \"cpu-total\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", + # Set datasource to name of your datasource + datasource="influxdb", + refId="A", + ), + AlertExpression( + refId="B", + expressionType=EXP_TYPE_REDUCE, + expression='A', + reduceFunction='mean', + reduceMode='dropNN' + ), + AlertExpression( + refId="C", + expressionType=EXP_TYPE_MATH, + expression='$B < 3' + ) + ], + annotations={ + "summary": "The database is down", + "runbook_url": "runbook-for-this-scenario.com/foo", + }, + labels={ + "environment": "prod", + "slack": "prod-alerts", + }, + evaluateFor="3m", + ) + ] +) diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 61a4d8f4..57285d61 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -756,7 +756,7 @@ def test_alertgroup(): group = G.AlertGroup( name=name, rules=[ - G.AlertRule( + G.AlertRulev8( title="My Important Alert!", triggers=[ ( @@ -784,11 +784,11 @@ def test_alertgroup(): assert output["rules"][0]["grafana_alert"]["rule_group"] == name -def test_alertrule(): +def test_alertrulev8(): title = "My Important Alert!" annotations = {"summary": "this alert fires when prod is down!!!"} labels = {"severity": "serious"} - rule = G.AlertRule( + rule = G.AlertRulev8( title=title, triggers=[ ( @@ -828,7 +828,7 @@ def test_alertrule_invalid_triggers(): # test that triggers is a list of [(Target, AlertCondition)] with pytest.raises(ValueError): - G.AlertRule( + G.AlertRulev8( title="Invalid rule", triggers=[ G.Target( @@ -839,7 +839,7 @@ def test_alertrule_invalid_triggers(): ) with pytest.raises(ValueError): - G.AlertRule( + G.AlertRulev8( title="Invalid rule", triggers=[ ( @@ -853,7 +853,7 @@ def test_alertrule_invalid_triggers(): ) with pytest.raises(ValueError): - G.AlertRule( + G.AlertRulev8( title="Invalid rule", triggers=[ ( @@ -867,6 +867,93 @@ def test_alertrule_invalid_triggers(): ) +def test_alertrulev9(): + title = "My Important Alert!" + annotations = {"summary": "this alert fires when prod is down!!!"} + labels = {"severity": "serious"} + condition = 'C' + rule = G.AlertRulev9( + title=title, + uid='alert1', + condition=condition, + triggers=[ + G.Target( + expr='query', + refId='A', + datasource='Prometheus', + ), + G.AlertExpression( + refId='B', + expressionType=G.EXP_TYPE_CLASSIC, + expression='A', + conditions=[ + G.AlertCondition( + evaluator=G.GreaterThan(3), + operator=G.OP_AND, + reducerType=G.RTYPE_LAST + ) + ] + ), + ], + annotations=annotations, + labels=labels, + evaluateFor="3m", + ) + + data = rule.to_json_data() + assert data['title'] == title + assert data['annotations'] == annotations + assert data['labels'] == labels + assert data['for'] == "3m" + assert data['condition'] == condition + + +def test_alertexpression(): + refId = 'D' + expression = 'C' + expressionType = G.EXP_TYPE_REDUCE + reduceFunction = G.EXP_REDUCER_FUNC_MAX + reduceMode = G.EXP_REDUCER_FUNC_DROP_NN + + alert_exp = G.AlertExpression( + refId=refId, + expression=expression, + expressionType=expressionType, + reduceFunction=reduceFunction, + reduceMode=reduceMode + ) + + data = alert_exp.to_json_data() + + assert data['refId'] == refId + assert data['datasourceUid'] == '-100' + assert data['model']['conditions'] == [] + assert data['model']['datasource'] == { + 'type': '__expr__', + 'uid': '-100' + } + assert data['model']['expression'] == expression + assert data['model']['refId'] == refId + assert data['model']['type'] == expressionType + assert data['model']['reducer'] == reduceFunction + assert data['model']['settings']['mode'] == reduceMode + + +def test_alertfilefasedfrovisioning(): + groups = [{ + 'foo': 'bar' + }] + + rules = G.AlertFileBasedProvisioning( + groups=groups + ) + + data = rules.to_json_data() + + assert data['apiVersion'] == 1 + assert data['groups'] == groups + + def test_worldmap(): data_source = 'dummy data source' targets = ['dummy_prom_query'] diff --git a/grafanalib/tests/test_examples.py b/grafanalib/tests/test_examples.py index baf1f73b..87d2bb27 100644 --- a/grafanalib/tests/test_examples.py +++ b/grafanalib/tests/test_examples.py @@ -25,6 +25,17 @@ def test_examples(): # Run alertgroup example alerts = glob.glob('{}/*.alertgroup.py'.format(examples_dir)) + assert len(alerts) == 2 + + stdout = io.StringIO() + for example in alerts: + with redirect_stdout(stdout): + ret = _gen.generate_alertgroup([example]) + assert ret == 0 + assert stdout.getvalue() != '' + + # Run file based provisioning of alerts example + alerts = glob.glob('{}/*.alertfilebasedprovisioning.py'.format(examples_dir)) assert len(alerts) == 1 stdout = io.StringIO() From ff156bb13dc3fa6b5df2abc8b5e62bde4ab8935e Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:00:23 +0000 Subject: [PATCH 349/403] Prep v0.7.0 release (#539) --- CHANGELOG.rst | 3 +-- setup.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 3f9dc996..4e153217 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,10 +2,9 @@ Changelog ========= -x.x.x (TBC) +0.7.0 (2022-10-02) =========== -* Added ... * Added Grafana 8.x new Alert Rule * Added Grafana 9.x new Alert Rule * Added ePict_ plugin. diff --git a/setup.py b/setup.py index e7cc0ce2..85bc236f 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ def local_file(name): # Versions should comply with PEP440. For a discussion on single-sourcing # the version across setup.py and the project code, see # https://packaging.python.org/en/latest/guides/single-sourcing-package-version/ - version='0.6.3', + version='0.7.0', description='Library for building Grafana dashboards', long_description=open(README).read(), url='https://github.com/weaveworks/grafanalib', From 10abb266836f309c7418171393c244971d8f1934 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 08:03:08 +0000 Subject: [PATCH 350/403] Bump lycheeverse/lychee-action from 1.5.1 to 1.5.2 (#541) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.5.1 to 1.5.2. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.5.1...v1.5.2) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 0c04835f..88ec2cb7 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.5.1 + uses: lycheeverse/lychee-action@v1.5.2 with: args: --verbose **/*.html - name: Fail if there were link errors From f269a7b7b86802bc254f071f066b50f0d3c1b0b8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 08:03:23 +0000 Subject: [PATCH 351/403] Bump sphinx-rtd-theme from 1.0.0 to 1.1.1 (#542) Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.0.0 to 1.1.1. - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.0.0...1.1.1) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index cf133465..543d740d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx == 5.3.0 -sphinx_rtd_theme == 1.0.0 \ No newline at end of file +sphinx_rtd_theme == 1.1.1 \ No newline at end of file From 8efa944e895635c9bf23c8fd6fdbddb87ba4a76f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 15:15:23 +0000 Subject: [PATCH 352/403] Bump lycheeverse/lychee-action from 1.5.2 to 1.5.4 (#546) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.5.2 to 1.5.4. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.5.2...v1.5.4) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 88ec2cb7..a8a45a8a 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.5.2 + uses: lycheeverse/lychee-action@v1.5.4 with: args: --verbose **/*.html - name: Fail if there were link errors From 75236aaf80796d50e7371fbbf529839de3495dde Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Mon, 9 Jan 2023 12:17:45 +0000 Subject: [PATCH 353/403] Make tests run on PR (#563) --- .github/workflows/run-tests.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 6d3fe23a..502a1b72 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,10 +1,5 @@ name: Run tests -on: - push: - branches: main - pull_request: - jobs: build-n-publish: name: Run tests From 147cb1715e60bec35701fc1243a6066de6272771 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 3 Feb 2023 15:11:09 +0000 Subject: [PATCH 354/403] Bump actions/setup-python from 4.3.0 to 4.5.0 (#565) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.3.0 to 4.5.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4.3.0...v4.5.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index a8a45a8a..2cfbd1b3 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v4.3.0 + uses: actions/setup-python@v4.5.0 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 2c0eb315..5f645277 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.7 - uses: actions/setup-python@v4.3.0 + uses: actions/setup-python@v4.5.0 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 502a1b72..234dbb90 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v4.3.0 + uses: actions/setup-python@v4.5.0 with: python-version: ${{ matrix.python }} - name: Run tests From 82f14bb051a92301897763343cd3723facdecafc Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Fri, 3 Feb 2023 16:22:41 +0000 Subject: [PATCH 355/403] Update readme doc tag from latest to main branch (#569) --- .github/workflows/run-tests.yml | 6 ++++-- README.rst | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 234dbb90..824f7df2 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,12 +1,14 @@ name: Run tests +on: [push, pull_request] + jobs: build-n-publish: - name: Run tests + name: Run tests - Python ${{ matrix.python }} runs-on: ubuntu-20.04 strategy: matrix: - python: ['3.7', '3.8', '3.9', '3.10'] + python: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 - name: Set up Python diff --git a/README.rst b/README.rst index a8b0941d..e480b651 100644 --- a/README.rst +++ b/README.rst @@ -2,10 +2,10 @@ Getting Started with grafanalib =============================== -.. image:: https://readthedocs.org/projects/grafanalib/badge/?version=latest +.. image:: https://readthedocs.org/projects/grafanalib/badge/?version=main :alt: Documentation Status :scale: 100% - :target: https://grafanalib.readthedocs.io/en/latest/?badge=latest + :target: https://grafanalib.readthedocs.io/en/latest/?badge=main Do you like `Grafana `_ but wish you could version your dashboard configuration? Do you find yourself repeating common patterns? If From dfd0a256e9dbdcb495c994b66b5968666525a393 Mon Sep 17 00:00:00 2001 From: Yamori <41197469+Gekko0114@users.noreply.github.com> Date: Sat, 4 Feb 2023 01:31:51 +0900 Subject: [PATCH 356/403] fix description of elastic search's param field (#556) --- grafanalib/elasticsearch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 0cc29beb..a01c5317 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -79,7 +79,7 @@ class CardinalityMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-cardinality-aggregation.html - :param field: name of elasticsearch field to provide the maximum for + :param field: name of elasticsearch field to provide the cardinality for :param id: id of the metric :param hide: show/hide the metric in the final panel display :param inline: script to apply to the data, using '_value' @@ -111,7 +111,7 @@ class AverageMetricAgg(object): https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html - :param field: name of elasticsearch field to provide the maximum for + :param field: name of elasticsearch metric aggregator to provide the average of :param id: id of the metric :param hide: show/hide the metric in the final panel display :param inline: script to apply to the data, using '_value' @@ -437,7 +437,7 @@ class ElasticsearchAlertCondition(AlertCondition): class MinMetricAgg(object): """An aggregator that provides the min. value among the values. https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-min-aggregation.html - :param field: name of elasticsearch field to provide the maximum for + :param field: name of elasticsearch field to provide the minimum for :param hide: show/hide the metric in the final panel display :param id: id of the metric :param inline: script to apply to the data, using '_value' @@ -468,7 +468,7 @@ def to_json_data(self): class PercentilesMetricAgg(object): """A multi-value metrics aggregation that calculates one or more percentiles over numeric values extracted from the aggregated documents https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-percentile-aggregation.html - :param field: name of elasticsearch field to provide the maximum for + :param field: name of elasticsearch field to provide the percentiles for :param hide: show/hide the metric in the final panel display :param id: id of the metric :param inline: script to apply to the data, using '_value' From 8507d2a16f23075a88fbce02d1b5ddf6850943cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Feb 2023 12:56:50 +0000 Subject: [PATCH 357/403] Bump sphinx-rtd-theme from 1.1.1 to 1.2.0 Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.1.1 to 1.2.0. - [Release notes](https://github.com/readthedocs/sphinx_rtd_theme/releases) - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.1.1...1.2.0) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 543d740d..42aca7d9 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx == 5.3.0 -sphinx_rtd_theme == 1.1.1 \ No newline at end of file +sphinx_rtd_theme == 1.2.0 \ No newline at end of file From 628a7d67e7f3bfd76da8336d2cd4db7eae8388e6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 12:56:41 +0000 Subject: [PATCH 358/403] Bump lycheeverse/lychee-action from 1.5.4 to 1.6.1 Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.5.4 to 1.6.1. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.5.4...v1.6.1) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 2cfbd1b3..fb0f436d 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.5.4 + uses: lycheeverse/lychee-action@v1.6.1 with: args: --verbose **/*.html - name: Fail if there were link errors From 2d06a29a4f59420e2c736c49a3c319f5bca91a11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Mar 2023 16:30:33 +0000 Subject: [PATCH 359/403] Bump sphinx from 5.3.0 to 6.1.3 Bumps [sphinx](https://github.com/sphinx-doc/sphinx) from 5.3.0 to 6.1.3. - [Release notes](https://github.com/sphinx-doc/sphinx/releases) - [Changelog](https://github.com/sphinx-doc/sphinx/blob/master/CHANGES) - [Commits](https://github.com/sphinx-doc/sphinx/compare/v5.3.0...v6.1.3) --- updated-dependencies: - dependency-name: sphinx dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 42aca7d9..e7edf286 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinx == 5.3.0 +sphinx == 6.1.3 sphinx_rtd_theme == 1.2.0 \ No newline at end of file From 7116beb5e9d705aac7e6ea395fcb2b5220a2c214 Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 6 Mar 2023 17:36:29 +0100 Subject: [PATCH 360/403] update to conform and test against all current pythons Signed-off-by: Daniel Holbach --- docs/getting-started.rst | 2 +- setup.py | 3 ++- tox.ini | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/getting-started.rst b/docs/getting-started.rst index d817f85e..08e0dd47 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -188,7 +188,7 @@ Support This library is in its very early stages. We'll probably make changes that break backwards compatibility, although we'll try hard not to. -grafanalib works with Python 3.7, 3.8, 3.9 and 3.10. +grafanalib works with Python 3.7, 3.8, 3.9, 3.10 and 3.11. Developing ========== diff --git a/setup.py b/setup.py index 85bc236f..3108e480 100644 --- a/setup.py +++ b/setup.py @@ -33,10 +33,11 @@ def local_file(name): 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', 'Topic :: System :: Monitoring', ], install_requires=[ diff --git a/tox.ini b/tox.ini index 9d4f500c..d5163f50 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py36, py37, py38, py39, py310 +envlist = py37, py38, py39, py310, py311 [testenv] commands = pytest -o junit_family=xunit2 --junitxml=test-results/junit-{envname}.xml From d1d50d18f79a385b4c3b2e4753e99b3a627bc3da Mon Sep 17 00:00:00 2001 From: Daniel Holbach Date: Mon, 27 Mar 2023 14:17:31 +0200 Subject: [PATCH 361/403] Remove myself from maintainers. I haven't found much time for grafanalib lately. It was a big pleasure to work together with all of you. All the best! Signed-off-by: Daniel Holbach --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 9759553b..bcde4db6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5,12 +5,12 @@ https://weave-community.slack.com/ in #grafanalib (https://weave-community.slack (obtain an invitation at https://slack.weave.works/). -Daniel Holbach, Weaveworks (github: @dholbach, slack: dholbach) James Gibson, BBC (github: @JamesGibo, slack: James G) Retired maintainers: - Bryan Boreham +- Daniel Holbach - Jonathan Lange - Matt Richter From 81bb8f727a47caa788722ac25347223958b46284 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 09:44:06 +0100 Subject: [PATCH 362/403] Bump lycheeverse/lychee-action from 1.6.1 to 1.8.0 (#586) Bumps [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action) from 1.6.1 to 1.8.0. - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v1.6.1...v1.8.0) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index fb0f436d..1e0fc235 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -27,7 +27,7 @@ jobs: - name: Link Checker id: lc - uses: lycheeverse/lychee-action@v1.6.1 + uses: lycheeverse/lychee-action@v1.8.0 with: args: --verbose **/*.html - name: Fail if there were link errors From 745d8ea530f1ba0cced28af92897ef0c73798a0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 09:44:19 +0100 Subject: [PATCH 363/403] Bump actions/setup-python from 4.5.0 to 4.6.0 (#579) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.5.0 to 4.6.0. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4.5.0...v4.6.0) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 1e0fc235..73641eee 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v4.5.0 + uses: actions/setup-python@v4.6.0 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 5f645277..26ed2b86 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.7 - uses: actions/setup-python@v4.5.0 + uses: actions/setup-python@v4.6.0 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 824f7df2..54e98f15 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v4.5.0 + uses: actions/setup-python@v4.6.0 with: python-version: ${{ matrix.python }} - name: Run tests From 37224f95706aae66ea6b74bc1952a020c10f2a67 Mon Sep 17 00:00:00 2001 From: Oscar van Leusen Date: Thu, 18 May 2023 10:03:50 +0100 Subject: [PATCH 364/403] fix: AlertRulev9 JSON schema is not correct (#544) * fix errors in grafanalib json schema for v9 alerts * fix test --- grafanalib/core.py | 12 +++++++----- grafanalib/tests/test_core.py | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 5430d6d5..70dbdb9b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1625,15 +1625,17 @@ def to_json_data(self): data += [trigger.to_json_data()] return { - "title": self.title, "uid": self.uid, - "condition": self.condition, "for": self.evaluateFor, "labels": self.labels, "annotations": self.annotations, - "data": data, - "noDataState": self.noDataAlertState, - "execErrState": self.errorAlertState + "grafana_alert": { + "title": self.title, + "condition": self.condition, + "data": data, + "no_data_state": self.noDataAlertState, + "exec_err_state": self.errorAlertState, + }, } diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 57285d61..a13130b7 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -901,11 +901,11 @@ def test_alertrulev9(): ) data = rule.to_json_data() - assert data['title'] == title assert data['annotations'] == annotations assert data['labels'] == labels assert data['for'] == "3m" - assert data['condition'] == condition + assert data['grafana_alert']['title'] == title + assert data['grafana_alert']['condition'] == condition def test_alertexpression(): From 028325b0c4045e97a652b1f08ecbded1053eadce Mon Sep 17 00:00:00 2001 From: Vincent CANDEAU Date: Thu, 18 May 2023 11:10:47 +0200 Subject: [PATCH 365/403] TimeSerie option ( Min, Max, Decimals ) (#501) * [ADD] Minimun options for Timeseries [ADD] Maximun options for Timeseries [ADD] Number of decimals displays options for Timeseries * [UPD] Changelog for merge request * [FIX] Typo regarless to the JamesGibo suggestion * [FIX] Typo regarless to the JamesGibo suggestion --------- Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- CHANGELOG.rst | 11 +++++++++-- grafanalib/core.py | 10 ++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4e153217..44037faf 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,8 +2,16 @@ Changelog ========= +0.x.x (?) +================== + +* Added ... +* Added Minimum option for Timeseries +* Added Maximum option for Timeseries +* Added Number of decimals displays option for Timeseries + 0.7.0 (2022-10-02) -=========== +================== * Added Grafana 8.x new Alert Rule * Added Grafana 9.x new Alert Rule @@ -17,7 +25,6 @@ Changelog .. _ePict: basehttps://grafana.com/grafana/plugins/larona-epict-panel/ - 0.6.3 (2022-03-30) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 70dbdb9b..3ee744cf 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2193,6 +2193,9 @@ class TimeSeries(Panel): single (Default), multi, none :param unit: units :param thresholdsStyleMode: thresholds style mode off (Default), area, line, line+area + :param valueMin: Minimum value for Panel + :param valueMax: Maximum value for Panel + :param valueDecimals: Number of display decimals """ axisPlacement = attr.ib(default='auto', validator=instance_of(str)) @@ -2246,6 +2249,10 @@ class TimeSeries(Panel): unit = attr.ib(default='', validator=instance_of(str)) thresholdsStyleMode = attr.ib(default='off', validator=instance_of(str)) + valueMin = attr.ib(default=None, validator=attr.validators.optional(instance_of(int))) + valueMax = attr.ib(default=None, validator=attr.validators.optional(instance_of(int))) + valueDecimals = attr.ib(default=None, validator=attr.validators.optional(instance_of(int))) + def to_json_data(self): return self.panel_json( { @@ -2281,6 +2288,9 @@ def to_json_data(self): }, }, 'mappings': self.mappings, + "min": self.valueMin, + "max": self.valueMax, + "decimals": self.valueDecimals, 'unit': self.unit }, 'overrides': self.overrides From c46a6005ea48d0f7c296388213e983e1325229e0 Mon Sep 17 00:00:00 2001 From: Filippo Pinton Date: Thu, 18 May 2023 11:16:18 +0200 Subject: [PATCH 366/403] Add support for `statistic` field in `CloudwatchMetricsTarget` (#543) * Add support for `statistic` field in `CloudwatchMetricsTarget` * Update grafanalib/cloudwatch.py --------- Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- grafanalib/cloudwatch.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py index 47d2c452..349a0ff8 100644 --- a/grafanalib/cloudwatch.py +++ b/grafanalib/cloudwatch.py @@ -26,7 +26,8 @@ class CloudwatchMetricsTarget(object): :param period: Cloudwatch data period :param refId: target reference id :param region: Cloudwatch region - :param statistics: Cloudwatch mathematic statistic + :param statistics: Cloudwatch mathematic statistics (to be deprecated, prefer `statistic` instead) + :param statistic: Cloudwatch mathematic statistic :param hide: controls if given metric is displayed on visualization :param datasource: Grafana datasource name """ @@ -41,6 +42,7 @@ class CloudwatchMetricsTarget(object): refId = attr.ib(default="") region = attr.ib(default="default") statistics = attr.ib(default=["Average"], validator=instance_of(list)) + statistic = attr.ib(default="Average") hide = attr.ib(default=False, validator=instance_of(bool)) datasource = attr.ib(default=None) @@ -58,6 +60,7 @@ def to_json_data(self): "refId": self.refId, "region": self.region, "statistics": self.statistics, + "statistic": self.statistic, "hide": self.hide, "datasource": self.datasource, } From 225ff488f50f99cd4afab889c75dcba8ee8475e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1lm=C3=A1n=20Neszl=C3=A9nyi?= <73647069+kamka427@users.noreply.github.com> Date: Thu, 18 May 2023 11:20:50 +0200 Subject: [PATCH 367/403] Add support for Bar chart panel (#547) * Add BarChart implementation * Add Unit tests for BarChart * Fix mismatched nesting * Add documentation for BarChart * Format attributes * Apply coding guidelines * Add new Changelog entry * Update documentation for BarChart * Add support for fixedColor --------- Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- CHANGELOG.rst | 6 +- grafanalib/core.py | 142 ++++++++++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 21 +++++ 3 files changed, 168 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 44037faf..a1de2b1d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,13 +2,17 @@ Changelog ========= + 0.x.x (?) ================== * Added ... * Added Minimum option for Timeseries * Added Maximum option for Timeseries -* Added Number of decimals displays option for Timeseries +* Added Number of decimals displays option for Timeseries* Added Bar_Chart_ panel support + +.. _Bar_Chart: basehttps://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/bar-chart/ + 0.7.0 (2022-10-02) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 3ee744cf..faa3e50b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -99,6 +99,7 @@ def to_json_data(self): NEWS_TYPE = 'news' HISTOGRAM_TYPE = 'histogram' AE3E_PLOTLY_TYPE = 'ae3e-plotly-panel' +BAR_CHART_TYPE = 'barchart' DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -4293,3 +4294,144 @@ def to_json_data(self): _deep_update(plotly["options"]["layout"], self.layout) _deep_update(plotly["options"]["configuration"], self.configuration) return plotly + + +@attr.s +class BarChart(Panel): + """Generates bar chart panel json structure + Grafana docs on Bar chart panel: https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/bar-chart/ + + :param orientation: Controls the orientation of the chart + :param xTickLabelRotation: Controls the rotation of bar labels + :param xTickLabelSpacing: Controls the spacing of bar labels + :param showValue: Controls the visibility of values + :param stacking: Controls the stacking of the bar chart + :param groupWidth: Controls the width of the group + :param barWidth: Controls the width of the bars + :param barRadius: Controls the radius of the bars + :param toolTipMode: Controls the style of tooltips + :param toolTipSort: Controls the sort order of tooltips, when toolTipMode is 'All' + :param showLegend: Controls the visibility of legends + :param legendDisplayMode: Controls the style of legends, if they are shown. + :param legendPlacement: Controls the placement of legends, if they are shown + :param legendCalcs: Controls the calculations to show on legends + :param lineWidth: Controls the width of lines + :param fillOpacity: Contorls the opacity of bars + :param gradientMode: Controls the gradient style of the bars + :param axisPlacement: Controls the axis placement + :param axisLabel: Controls the axis labels + :param axisColorMode: Controls the axis color style + :param scaleDistributionType: Controls the type of distribution + :param axisCenteredZero: Controls the centering of the axis + :param hideFromTooltip: Controls the hiding of tooltips + :param hideFromViz: Controls the hiding of bars + :param hideFromLegend: Controls the hiding of legends + :param colorMode: Controls the color palette of the bars + :param fixedColor: Controls the color of the bars, when the colorMode is fixed + :param mappings: Controls the mapping of values + :param thresholdsMode: Controls the style threshold + :param thresholdSteps: Controls the treshold steps + :param overrides: Controls the overriding of certain datas base characteristics + """ + orientation = attr.ib(default='auto', validator=instance_of(str)) + xTickLabelRotation = attr.ib(default=0, validator=instance_of(int)) + xTickLabelSpacing = attr.ib(default=0, validator=instance_of(int)) + showValue = attr.ib(default='auto', validator=instance_of(str)) + stacking = attr.ib(default='none', validator=instance_of(str)) + groupWidth = attr.ib(default=0.7, validator=instance_of(float)) + barWidth = attr.ib(default=0.97, validator=instance_of(float)) + barRadius = attr.ib(default=0.0, validator=instance_of(float)) + tooltipMode = attr.ib(default='single', validator=instance_of(str)) + tooltipSort = attr.ib(default='none', validator=instance_of(str)) + showLegend = attr.ib(default=True, validator=instance_of(bool)) + legendDisplayMode = attr.ib(default='list', validator=instance_of(str)) + legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) + legendCalcs = attr.ib(default=[], validator=instance_of(list)) + lineWidth = attr.ib(default=1, validator=instance_of(int)) + fillOpacity = attr.ib(default=80, validator=instance_of(int)) + gradientMode = attr.ib(default='none', validator=instance_of(str)) + axisPlacement = attr.ib(default='auto', validator=instance_of(str)) + axisLabel = attr.ib(default='', validator=instance_of(str)) + axisColorMode = attr.ib(default='text', validator=instance_of(str)) + scaleDistributionType = attr.ib(default='linear', validator=instance_of(str)) + axisCenteredZero = attr.ib(default=False, validator=instance_of(bool)) + hideFromTooltip = attr.ib(default=False, validator=instance_of(bool)) + hideFromViz = attr.ib(default=False, validator=instance_of(bool)) + hideFromLegend = attr.ib(default=False, validator=instance_of(bool)) + colorMode = attr.ib(default='palette-classic', validator=instance_of(str)) + fixedColor = attr.ib(default='blue', validator=instance_of(str)) + mappings = attr.ib(default=[], validator=instance_of(list)) + thresholdsMode = attr.ib(default='absolute', validator=instance_of(str)) + thresholdSteps = attr.ib( + default=attr.Factory(lambda: [ + { + 'value': None, + 'color': 'green' + }, + { + 'value': 80, + 'color': 'red' + } + ]), + validator=instance_of(list) + ) + overrides = attr.ib(default=[], validator=instance_of(list)) + + def to_json_data(self): + bar_chart = self.panel_json( + { + 'options': { + 'orientation': self.orientation, + 'xTickLabelRotation': self.xTickLabelRotation, + 'xTickLabelSpacing': self.xTickLabelSpacing, + 'showValue': self.showValue, + 'stacking': self.stacking, + 'groupWidth': self.groupWidth, + 'barWidth': self.barWidth, + 'barRadius': self.barRadius, + 'tooltip': { + 'mode': self.tooltipMode, + 'sort': self.tooltipSort + }, + 'legend': { + 'showLegend': self.showLegend, + 'displayMode': self.legendDisplayMode, + 'placement': self.legendPlacement, + 'calcs': self.legendCalcs + }, + }, + 'fieldConfig': { + 'defaults': { + 'custom': { + 'lineWidth': self.lineWidth, + 'fillOpacity': self.fillOpacity, + 'gradientMode': self.gradientMode, + 'axisPlacement': self.axisPlacement, + 'axisLabel': self.axisLabel, + 'axisColorMode': self.axisColorMode, + 'scaleDistribution': { + 'type': self.scaleDistributionType + }, + 'axisCenteredZero': self.axisCenteredZero, + 'hideFrom': { + 'tooltip': self.hideFromTooltip, + 'viz': self.hideFromViz, + 'legend': self.hideFromLegend + } + }, + 'color': { + 'mode': self.colorMode, + 'fixedColor': self.fixedColor if self.colorMode == 'fixed' else 'none' + }, + 'mappings': self.mappings, + 'thresholds': { + 'mode': self.thresholdsMode, + 'steps': self.thresholdSteps + } + }, + 'overrides': self.overrides + }, + 'type': BAR_CHART_TYPE + } + ) + return bar_chart diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index a13130b7..fee0264c 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -1088,6 +1088,27 @@ def test_ae3e_plotly(): assert data["options"]["layout"] == layout +def test_barchart(): + data_source = "dummy data source" + targets = ["dummy_prom_query"] + title = "dummy title" + panel = G.BarChart(data_source, targets, title) + data = panel.to_json_data() + assert data["targets"] == targets + assert data["datasource"] == data_source + assert data["title"] == title + assert data["options"] is not None + assert data["fieldConfig"] is not None + assert data["options"]["orientation"] == 'auto' + assert data["fieldConfig"]["defaults"]["color"]["mode"] == 'palette-classic' + + panel = G.BarChart(data_source, targets, title, orientation='horizontal', axisCenteredZero=True, showLegend=False) + data = panel.to_json_data() + assert data["options"]["orientation"] == 'horizontal' + assert data["fieldConfig"]["defaults"]["custom"]["axisCenteredZero"] + assert not data["options"]["legend"]["showLegend"] + + def test_target_invalid(): with pytest.raises(ValueError, match=r"target should have non-empty 'refId' attribute"): return G.AlertCondition( From 79450cf950690a9cc5fdffc01754cf9ce46dbc52 Mon Sep 17 00:00:00 2001 From: Oscar van Leusen Date: Thu, 18 May 2023 10:22:30 +0100 Subject: [PATCH 368/403] expose nowDelay field (#554) --- grafanalib/core.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index faa3e50b..c28d7ced 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1056,10 +1056,15 @@ class TimePicker(object): :param refreshIntervals: dashboard auto-refresh interval options :param timeOptions: dashboard time range options + :param nowDelay: exclude recent data that may be incomplete, as a + number + unit (s: second, m: minute, h: hour, etc) :param hidden: hide the time picker from dashboard """ refreshIntervals = attr.ib() timeOptions = attr.ib() + nowDelay = attr.ib( + default=None, + ) hidden = attr.ib( default=False, validator=instance_of(bool), @@ -1069,6 +1074,7 @@ def to_json_data(self): return { 'refresh_intervals': self.refreshIntervals, 'time_options': self.timeOptions, + 'nowDelay': self.nowDelay, 'hidden': self.hidden } From 2a30b441216aad5dd998a5317ae04e4616545eb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A1lm=C3=A1n=20Neszl=C3=A9nyi?= <73647069+kamka427@users.noreply.github.com> Date: Thu, 18 May 2023 11:32:23 +0200 Subject: [PATCH 369/403] Update SqlTarget to support parsing queries from files (#555) * Update SqlTarget to support parsing queries from files * Update test files to be more general --------- Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- CHANGELOG.rst | 1 + grafanalib/core.py | 14 ++++++++++ .../sqltarget_example_files/example.sql | 3 ++ .../example_with_params.sql | 3 ++ grafanalib/tests/test_core.py | 28 +++++++++++++++++++ 5 files changed, 49 insertions(+) create mode 100644 grafanalib/tests/examples/sqltarget_example_files/example.sql create mode 100644 grafanalib/tests/examples/sqltarget_example_files/example_with_params.sql diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a1de2b1d..7b18f65b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -10,6 +10,7 @@ Changelog * Added Minimum option for Timeseries * Added Maximum option for Timeseries * Added Number of decimals displays option for Timeseries* Added Bar_Chart_ panel support +* Extended SqlTarget to support parsing queries from files .. _Bar_Chart: basehttps://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/bar-chart/ diff --git a/grafanalib/core.py b/grafanalib/core.py index c28d7ced..54e6028c 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -609,6 +609,20 @@ class SqlTarget(Target): rawSql = attr.ib(default="") rawQuery = attr.ib(default=True) + srcFilePath = attr.ib(default="", validator=instance_of(str)) + sqlParams = attr.ib(default={}, validator=instance_of(dict)) + + def __attrs_post_init__(self): + """Override rawSql if a path to a source file is provided, + if it is a parameterized query, fill in the parameters. + srcFilePath: this will containt the path to the source file + sqlParams: this will contain the sql parameters to use in the read query + """ + if self.srcFilePath: + with open(self.srcFilePath, "r") as f: + self.rawSql = f.read() + if self.sqlParams is not None: + self.rawSql = self.rawSql.format(**self.sqlParams) def to_json_data(self): """Override the Target to_json_data to add additional fields. diff --git a/grafanalib/tests/examples/sqltarget_example_files/example.sql b/grafanalib/tests/examples/sqltarget_example_files/example.sql new file mode 100644 index 00000000..22a4747b --- /dev/null +++ b/grafanalib/tests/examples/sqltarget_example_files/example.sql @@ -0,0 +1,3 @@ +SELECT example, count(id) +FROM test +GROUP BY example; diff --git a/grafanalib/tests/examples/sqltarget_example_files/example_with_params.sql b/grafanalib/tests/examples/sqltarget_example_files/example_with_params.sql new file mode 100644 index 00000000..cbc1242b --- /dev/null +++ b/grafanalib/tests/examples/sqltarget_example_files/example_with_params.sql @@ -0,0 +1,3 @@ +SELECT example +FROM test +WHERE example='{example}' AND example_date BETWEEN '{starting_date}' AND '{ending_date}'; diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index fee0264c..f8178e87 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -1135,3 +1135,31 @@ def test_sql_target(): ) assert t.to_json_data()["targets"][0].rawQuery is True assert t.to_json_data()["targets"][0].rawSql == "SELECT * FROM example" + + +def test_sql_target_with_source_files(): + t = G.Table( + dataSource="some data source", + targets=[ + G.SqlTarget(srcFilePath="grafanalib/tests/examples/sqltarget_example_files/example.sql"), + ], + title="table title", + ) + assert t.to_json_data()["targets"][0].rawQuery is True + assert t.to_json_data()["targets"][0].rawSql == "SELECT example, count(id)\nFROM test\nGROUP BY example;\n" + print(t.to_json_data()["targets"][0]) + + t = G.Table( + dataSource="some data source", + targets=[ + G.SqlTarget(srcFilePath="grafanalib/tests/examples/sqltarget_example_files/example_with_params.sql", sqlParams={ + "example": "example", + "starting_date": "1970-01-01", + "ending_date": "1971-01-01", + },), + ], + title="table title", + ) + assert t.to_json_data()["targets"][0].rawQuery is True + assert t.to_json_data()["targets"][0].rawSql == "SELECT example\nFROM test\nWHERE example='example' AND example_date BETWEEN '1970-01-01' AND '1971-01-01';\n" + print(t.to_json_data()["targets"][0]) From bfdae85a19048d2ea1f87c91b4b1207059807bbc Mon Sep 17 00:00:00 2001 From: Ran K Date: Thu, 18 May 2023 11:01:36 +0100 Subject: [PATCH 370/403] fix: AlertRulev9 does not support CloudwatchMetricsTarget (#560) * fix errors in grafanalib json schema for v9 alerts * fix test * CloudwatchMetricsTarget is not a Target * Support again Grafana9 complex alert conditions in a single expression * Fix CloudwatchLogsInsightsTarget should be a Target --------- Co-authored-by: Oscar van Leusen Co-authored-by: Ran Katzir --- grafanalib/cloudwatch.py | 5 +++-- grafanalib/core.py | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py index 349a0ff8..951b8e78 100644 --- a/grafanalib/cloudwatch.py +++ b/grafanalib/cloudwatch.py @@ -3,10 +3,11 @@ import attr from attr.validators import instance_of +from grafanalib.core import Target @attr.s -class CloudwatchMetricsTarget(object): +class CloudwatchMetricsTarget(Target): """ Generates Cloudwatch target JSON structure. @@ -67,7 +68,7 @@ def to_json_data(self): @attr.s -class CloudwatchLogsInsightsTarget(object): +class CloudwatchLogsInsightsTarget(Target): """ Generates Cloudwatch Logs Insights target JSON structure. diff --git a/grafanalib/core.py b/grafanalib/core.py index 54e6028c..e10552ae 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1331,7 +1331,8 @@ def to_json_data(self): for condition in self.conditions: # discard unused features of condition as of grafana 8.x condition.useNewAlerts = True - condition.target = Target(refId=self.expression) + if condition.target is None: + condition.target = Target(refId=self.expression) conditions += [condition.to_json_data()] expression = { @@ -1453,7 +1454,7 @@ def is_valid_triggersv9(instance, attribute, value): """Validator for AlertRule triggers for Grafana v9""" for trigger in value: if not (isinstance(trigger, Target) or isinstance(trigger, AlertExpression)): - raise ValueError(f"{attribute.name} must either be a Target or AlertCondition") + raise ValueError(f"{attribute.name} must either be a Target or AlertExpression") if isinstance(trigger, Target): is_valid_target(instance, "alert trigger target", trigger) From 2ca57fc08f7661891908aa5aee67d04d8d670dda Mon Sep 17 00:00:00 2001 From: Jim Beatty Date: Thu, 18 May 2023 10:17:43 -0400 Subject: [PATCH 371/403] Add support for Elasticsearch Rate Metric Aggregator (#583) * Add support for Elasticsearch Rate Metric Aggregator * fix linting error Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> * Add import of attr.validators.in_ --------- Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- grafanalib/elasticsearch.py | 48 ++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index a01c5317..97269836 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -2,7 +2,7 @@ import attr import itertools -from attr.validators import instance_of +from attr.validators import in_, instance_of from grafanalib.core import AlertCondition DATE_HISTOGRAM_DEFAULT_FIELD = 'time_iso8601' @@ -498,3 +498,49 @@ def to_json_data(self): 'inlineScript': self.inline, 'settings': self.settings, } + + +@attr.s +class RateMetricAgg(object): + """An aggregator that provides the rate of the values. + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-rate-aggregation.html + :param field: name of elasticsearch field to provide the sum over + :param hide: show/hide the metric in the final panel display + :param id: id of the metric + :param unit: calendar interval to group by + supported calendar intervals + https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-datehistogram-aggregation.html#calendar_intervals + "minute" + "hour" + "day" + "week" + "month" + "quarter" + "year" + :param mode: sum or count the values + :param script: script to apply to the data, using '_value' + """ + + field = attr.ib(default="", validator=instance_of(str)) + id = attr.ib(default=0, validator=instance_of(int)) + hide = attr.ib(default=False, validator=instance_of(bool)) + unit = attr.ib(default="", validator=instance_of(str)) + mode = attr.ib(default="", validator=in_(["", "value_count", "sum"])) + script = attr.ib(default="", validator=instance_of(str)) + + def to_json_data(self): + self.settings = {} + + if self.mode: + self.settings["mode"] = self.mode + + if self.script: + self.settings["script"] = self.script + + return { + "id": str(self.id), + "hide": self.hide, + "field": self.field, + "settings": self.settings, + "type": "rate", + } From 5cd3d37a85ac99c6324596ae03e99295cb36ffaa Mon Sep 17 00:00:00 2001 From: Ali Essam Date: Thu, 18 May 2023 17:24:38 +0300 Subject: [PATCH 372/403] Fix AlertCondition backwards compatibility (#584) * Fix AlertCondition backward compatibility * Update Changelog --------- Co-authored-by: JamesGibo <22477854+JamesGibo@users.noreply.github.com> --- CHANGELOG.rst | 2 +- grafanalib/core.py | 4 ++++ grafanalib/tests/test_core.py | 27 +++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7b18f65b..2da345a6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,7 +2,6 @@ Changelog ========= - 0.x.x (?) ================== @@ -11,6 +10,7 @@ Changelog * Added Maximum option for Timeseries * Added Number of decimals displays option for Timeseries* Added Bar_Chart_ panel support * Extended SqlTarget to support parsing queries from files +* Fix AlertCondition backwards compatibility (``useNewAlerts`` default to ``False``) .. _Bar_Chart: basehttps://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/bar-chart/ diff --git a/grafanalib/core.py b/grafanalib/core.py index e10552ae..aeb1cb33 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1197,6 +1197,9 @@ class AlertCondition(object): RTYPE_DIFF = 'diff' RTYPE_PERCENT_DIFF = 'percent_diff' RTYPE_COUNT_NON_NULL = 'count_non_null' + :param useNewAlerts: Whether or not the alert condition is used as part of the Grafana 8.x alerts. + Defaults to False for compatibility with old Grafana alerts, but automatically overridden to true + when used inside ``AlertExpression`` or ``AlertRulev8`` :param type: CTYPE_* """ @@ -1205,6 +1208,7 @@ class AlertCondition(object): timeRange = attr.ib(default=None, validator=attr.validators.optional(attr.validators.instance_of(TimeRange))) operator = attr.ib(default=OP_AND) reducerType = attr.ib(default=RTYPE_LAST) + useNewAlerts = attr.ib(default=False) type = attr.ib(default=CTYPE_QUERY, kw_only=True) diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index f8178e87..2b03610b 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -954,6 +954,33 @@ def test_alertfilefasedfrovisioning(): assert data['groups'] == groups +def test_alertCondition_useNewAlerts_default(): + alert_condition = G.AlertCondition( + G.Target(refId="A"), + G.Evaluator('a', 'b'), + G.TimeRange('5', '6'), + 'd', + 'e' + ) + data = alert_condition.to_json_data() + assert data['query']['model'] is not None + assert len(data['query']['params']) == 3 + + +def test_alertCondition_useNewAlerts_true(): + alert_condition = G.AlertCondition( + G.Target(refId="A"), + G.Evaluator('a', 'b'), + G.TimeRange('5', '6'), + 'd', + 'e', + useNewAlerts=True + ) + data = alert_condition.to_json_data() + assert 'model' not in data['query'] + assert len(data['query']['params']) == 1 + + def test_worldmap(): data_source = 'dummy data source' targets = ['dummy_prom_query'] From ad017f0489f67e3d8405ebd7c09971f4d7c1ae4e Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Thu, 18 May 2023 15:38:36 +0100 Subject: [PATCH 373/403] Fix document builds by bumping python version to 3.10 (#591) --- .readthedocs.yml | 2 +- README.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 2ecec439..27e9f5da 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -18,7 +18,7 @@ formats: all # Optionally set the version of Python and requirements required to build your docs python: - version: 3.7 + version: 3.10 install: - requirements: docs/requirements.txt - method: setuptools diff --git a/README.rst b/README.rst index e480b651..5762d975 100644 --- a/README.rst +++ b/README.rst @@ -5,7 +5,7 @@ Getting Started with grafanalib .. image:: https://readthedocs.org/projects/grafanalib/badge/?version=main :alt: Documentation Status :scale: 100% - :target: https://grafanalib.readthedocs.io/en/latest/?badge=main + :target: https://grafanalib.readthedocs.io/en/main Do you like `Grafana `_ but wish you could version your dashboard configuration? Do you find yourself repeating common patterns? If From f600dc16779bfdf085b0fbd770eb2bca2fd6e15d Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Thu, 18 May 2023 15:45:37 +0100 Subject: [PATCH 374/403] Readthedocs (#592) * Fix document builds by bumping python version to 3.10 * Update syntax of read the docs build to support python > 3.8 --- .readthedocs.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 27e9f5da..d10352fa 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -5,6 +5,12 @@ # Required version: 2 +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.10" + # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py @@ -16,7 +22,7 @@ sphinx: # Optionally build your docs in additional formats such as PDF and ePub formats: all -# Optionally set the version of Python and requirements required to build your docs +# Optionally declare the Python requirements required to build your docs python: version: 3.10 install: From f19bda82db22cf08fd0dcb07f41f762f056739a1 Mon Sep 17 00:00:00 2001 From: JamesGibo <22477854+JamesGibo@users.noreply.github.com> Date: Thu, 18 May 2023 15:48:42 +0100 Subject: [PATCH 375/403] Remove readthedocs build python version tag --- .readthedocs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index d10352fa..cee51dff 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -24,7 +24,6 @@ formats: all # Optionally declare the Python requirements required to build your docs python: - version: 3.10 install: - requirements: docs/requirements.txt - method: setuptools From 45bb370d1a46faf90934f31eb3957327de815d5a Mon Sep 17 00:00:00 2001 From: Jim Beatty Date: Mon, 22 May 2023 11:11:01 -0400 Subject: [PATCH 376/403] Add unit test for RateMetricAgg in elasticsearch (#594) * Add unit test for RateMetricAgg in elasticsearch * Add missing code for `unit` param * Fix formatting in Changelog * Add missing changelog for Rate Aggregation --- CHANGELOG.rst | 10 ++++--- grafanalib/elasticsearch.py | 3 ++ grafanalib/tests/test_elasticsearch.py | 41 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 grafanalib/tests/test_elasticsearch.py diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2da345a6..7c480404 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,12 +8,14 @@ Changelog * Added ... * Added Minimum option for Timeseries * Added Maximum option for Timeseries -* Added Number of decimals displays option for Timeseries* Added Bar_Chart_ panel support +* Added Number of decimals displays option for Timeseries +* Added Bar_Chart_ panel support * Extended SqlTarget to support parsing queries from files * Fix AlertCondition backwards compatibility (``useNewAlerts`` default to ``False``) +* Added RateMetricAgg_ for ElasticSearch -.. _Bar_Chart: basehttps://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/bar-chart/ - +.. _`Bar_Chart`: https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/bar-chart/ +.. _`RateMetricAgg`: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-rate-aggregation.html 0.7.0 (2022-10-02) ================== @@ -28,7 +30,7 @@ Changelog * Fix typo in unit constant ``GIGA_WATT`` (was ``GAGA_WATT``) * Fix typo in unit constant ``NORMAL_CUBIC_METER`` (was ``NORMAIL_CUBIC_METER``) -.. _ePict: basehttps://grafana.com/grafana/plugins/larona-epict-panel/ +.. _`ePict`: https://grafana.com/grafana/plugins/larona-epict-panel/ 0.6.3 (2022-03-30) ================== diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 97269836..67158c1d 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -531,6 +531,9 @@ class RateMetricAgg(object): def to_json_data(self): self.settings = {} + if self.unit: + self.settings["unit"] = self.unit + if self.mode: self.settings["mode"] = self.mode diff --git a/grafanalib/tests/test_elasticsearch.py b/grafanalib/tests/test_elasticsearch.py new file mode 100644 index 00000000..61361c5f --- /dev/null +++ b/grafanalib/tests/test_elasticsearch.py @@ -0,0 +1,41 @@ +"""Tests for elasticsearch.""" + +import grafanalib.elasticsearch as E +import pytest + + +def test_rate_metric_agg(): + t = E.RateMetricAgg() + json_data = t.to_json_data() + + assert json_data["id"] == "0" + assert json_data["hide"] is False + assert json_data["field"] == "" + assert len(json_data["settings"]) == 0 + assert json_data["type"] == "rate" + assert len(json_data) == 5 + + t = E.RateMetricAgg( + field="some-field", + hide=True, + id=2, + unit="minute", + mode="sum", + script="some script" + ) + json_data = t.to_json_data() + + assert json_data["id"] == "2" + assert json_data["hide"] is True + assert json_data["field"] == "some-field" + assert len(json_data["settings"]) == 3 + assert json_data["settings"]["unit"] == "minute" + assert json_data["settings"]["mode"] == "sum" + assert json_data["settings"]["script"] == "some script" + assert json_data["type"] == "rate" + assert len(json_data) == 5 + + with pytest.raises(ValueError): + t = E.RateMetricAgg( + mode="invalid mode" + ) From a47398675ce05dec621adc3a79c3886cb2205b38 Mon Sep 17 00:00:00 2001 From: Ujwal Kumar Date: Mon, 22 May 2023 20:45:29 +0530 Subject: [PATCH 377/403] Replace occurence of default={} with factory=dict and default=[] with factory=list (#593) --- grafanalib/azuremonitor.py | 2 +- grafanalib/cloudwatch.py | 6 +++--- grafanalib/core.py | 40 ++++++++++++++++++------------------- grafanalib/elasticsearch.py | 4 ++-- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/grafanalib/azuremonitor.py b/grafanalib/azuremonitor.py index a9afc643..7cb324e8 100644 --- a/grafanalib/azuremonitor.py +++ b/grafanalib/azuremonitor.py @@ -24,7 +24,7 @@ class AzureMonitorMetricsTarget(object): """ aggregation = attr.ib(default="Total") - dimensionFilters = attr.ib(default=[], validator=instance_of(list)) + dimensionFilters = attr.ib(factory=list, validator=instance_of(list)) metricDefinition = attr.ib(default="") metricName = attr.ib(default="") metricNamespace = attr.ib(default="") diff --git a/grafanalib/cloudwatch.py b/grafanalib/cloudwatch.py index 951b8e78..a9a22248 100644 --- a/grafanalib/cloudwatch.py +++ b/grafanalib/cloudwatch.py @@ -33,7 +33,7 @@ class CloudwatchMetricsTarget(Target): :param datasource: Grafana datasource name """ alias = attr.ib(default="") - dimensions = attr.ib(default={}, validator=instance_of(dict)) + dimensions = attr.ib(factory=dict, validator=instance_of(dict)) expression = attr.ib(default="") id = attr.ib(default="") matchExact = attr.ib(default=True, validator=instance_of(bool)) @@ -90,11 +90,11 @@ class CloudwatchLogsInsightsTarget(Target): """ expression = attr.ib(default="") id = attr.ib(default="") - logGroupNames = attr.ib(default=[], validator=instance_of(list)) + logGroupNames = attr.ib(factory=list, validator=instance_of(list)) namespace = attr.ib(default="") refId = attr.ib(default="") region = attr.ib(default="default") - statsGroups = attr.ib(default=[], validator=instance_of(list)) + statsGroups = attr.ib(factory=list, validator=instance_of(list)) hide = attr.ib(default=False, validator=instance_of(bool)) datasource = attr.ib(default=None) diff --git a/grafanalib/core.py b/grafanalib/core.py index aeb1cb33..ed109763 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -610,7 +610,7 @@ class SqlTarget(Target): rawSql = attr.ib(default="") rawQuery = attr.ib(default=True) srcFilePath = attr.ib(default="", validator=instance_of(str)) - sqlParams = attr.ib(default={}, validator=instance_of(dict)) + sqlParams = attr.ib(factory=dict, validator=instance_of(dict)) def __attrs_post_init__(self): """Override rawSql if a path to a source file is provided, @@ -1496,8 +1496,8 @@ class AlertRulev8(object): title = attr.ib() triggers = attr.ib(validator=is_valid_triggers) - annotations = attr.ib(default={}, validator=instance_of(dict)) - labels = attr.ib(default={}, validator=instance_of(dict)) + annotations = attr.ib(factory=dict, validator=instance_of(dict)) + labels = attr.ib(factory=dict, validator=instance_of(dict)) evaluateInterval = attr.ib(default=DEFAULT_ALERT_EVALUATE_INTERVAL, validator=instance_of(str)) evaluateFor = attr.ib(default=DEFAULT_ALERT_EVALUATE_FOR, validator=instance_of(str)) @@ -1604,9 +1604,9 @@ class AlertRulev9(object): """ title = attr.ib() - triggers = attr.ib(default=[], validator=is_valid_triggersv9) - annotations = attr.ib(default={}, validator=instance_of(dict)) - labels = attr.ib(default={}, validator=instance_of(dict)) + triggers = attr.ib(factory=list, validator=is_valid_triggersv9) + annotations = attr.ib(factory=dict, validator=instance_of(dict)) + labels = attr.ib(factory=dict, validator=instance_of(dict)) evaluateFor = attr.ib(default=DEFAULT_ALERT_EVALUATE_FOR, validator=instance_of(str)) noDataAlertState = attr.ib( @@ -1942,7 +1942,7 @@ class ePict(Panel): autoScale = attr.ib(default=True, validator=instance_of(bool)) boxes = attr.ib( - default=[], + factory=list, validator=attr.validators.deep_iterable( member_validator=instance_of(ePictBox), iterable_validator=instance_of(list), @@ -2234,7 +2234,7 @@ class TimeSeries(Panel): legendDisplayMode = attr.ib(default='list', validator=instance_of(str)) legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) legendCalcs = attr.ib( - default=[], + factory=list, validator=attr.validators.deep_iterable( member_validator=in_([ 'lastNotNull', @@ -2270,7 +2270,7 @@ class TimeSeries(Panel): scaleDistributionLog = attr.ib(default=2, validator=instance_of(int)) spanNulls = attr.ib(default=False, validator=instance_of(bool)) showPoints = attr.ib(default='auto', validator=instance_of(str)) - stacking = attr.ib(default={}, validator=instance_of(dict)) + stacking = attr.ib(factory=dict, validator=instance_of(dict)) tooltipMode = attr.ib(default='single', validator=instance_of(str)) unit = attr.ib(default='', validator=instance_of(str)) thresholdsStyleMode = attr.ib(default='off', validator=instance_of(str)) @@ -2523,21 +2523,21 @@ class Discrete(Panel): showTransitionCount = attr.ib(default=None) colorMaps = attr.ib( - default=[], + factory=list, validator=attr.validators.deep_iterable( member_validator=instance_of(DiscreteColorMappingItem), iterable_validator=instance_of(list), ), ) rangeMaps = attr.ib( - default=[], + factory=list, validator=attr.validators.deep_iterable( member_validator=instance_of(RangeMap), iterable_validator=instance_of(list), ), ) valueMaps = attr.ib( - default=[], + factory=list, validator=attr.validators.deep_iterable( member_validator=instance_of(ValueMap), iterable_validator=instance_of(list), @@ -2798,7 +2798,7 @@ class StatValueMappings(object): """ mappingItems = attr.ib( - default=[], + factory=list, validator=attr.validators.deep_iterable( member_validator=attr.validators.instance_of(StatValueMappingItem), iterable_validator=attr.validators.instance_of(list), @@ -3540,7 +3540,7 @@ class StatusmapColor(object): colorScheme = attr.ib(default='GnYlRd', validator=instance_of(str)) exponent = attr.ib(default=0.5, validator=instance_of(float)) mode = attr.ib(default='spectrum', validator=instance_of(str)) - thresholds = attr.ib(default=[], validator=instance_of(list)) + thresholds = attr.ib(factory=list, validator=instance_of(list)) max = attr.ib(default=None) min = attr.ib(default=None) @@ -3749,13 +3749,13 @@ class PieChartv2(Panel): :param unit: units """ - custom = attr.ib(default={}, validator=instance_of(dict)) + custom = attr.ib(factory=dict, validator=instance_of(dict)) colorMode = attr.ib(default='palette-classic', validator=instance_of(str)) legendDisplayMode = attr.ib(default='list', validator=instance_of(str)) legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) - legendValues = attr.ib(default=[], validator=instance_of(list)) + legendValues = attr.ib(factory=list, validator=instance_of(list)) mappings = attr.ib(default=attr.Factory(list)) - overrides = attr.ib(default=[], validator=instance_of(list)) + overrides = attr.ib(factory=list, validator=instance_of(list)) pieType = attr.ib(default='pie', validator=instance_of(str)) reduceOptionsCalcs = attr.ib(default=['lastNotNull'], validator=instance_of(list)) reduceOptionsFields = attr.ib(default='', validator=instance_of(str)) @@ -4371,7 +4371,7 @@ class BarChart(Panel): showLegend = attr.ib(default=True, validator=instance_of(bool)) legendDisplayMode = attr.ib(default='list', validator=instance_of(str)) legendPlacement = attr.ib(default='bottom', validator=instance_of(str)) - legendCalcs = attr.ib(default=[], validator=instance_of(list)) + legendCalcs = attr.ib(factory=list, validator=instance_of(list)) lineWidth = attr.ib(default=1, validator=instance_of(int)) fillOpacity = attr.ib(default=80, validator=instance_of(int)) gradientMode = attr.ib(default='none', validator=instance_of(str)) @@ -4385,7 +4385,7 @@ class BarChart(Panel): hideFromLegend = attr.ib(default=False, validator=instance_of(bool)) colorMode = attr.ib(default='palette-classic', validator=instance_of(str)) fixedColor = attr.ib(default='blue', validator=instance_of(str)) - mappings = attr.ib(default=[], validator=instance_of(list)) + mappings = attr.ib(factory=list, validator=instance_of(list)) thresholdsMode = attr.ib(default='absolute', validator=instance_of(str)) thresholdSteps = attr.ib( default=attr.Factory(lambda: [ @@ -4400,7 +4400,7 @@ class BarChart(Panel): ]), validator=instance_of(list) ) - overrides = attr.ib(default=[], validator=instance_of(list)) + overrides = attr.ib(factory=list, validator=instance_of(list)) def to_json_data(self): bar_chart = self.panel_json( diff --git a/grafanalib/elasticsearch.py b/grafanalib/elasticsearch.py index 67158c1d..35f1a9d4 100644 --- a/grafanalib/elasticsearch.py +++ b/grafanalib/elasticsearch.py @@ -247,7 +247,7 @@ class BucketScriptAgg(object): :param id: id of the aggregator :param hide: show/hide the metric in the final panel display """ - fields = attr.ib(default={}, validator=instance_of(dict)) + fields = attr.ib(factory=dict, validator=instance_of(dict)) id = attr.ib(default=0, validator=instance_of(int)) hide = attr.ib(default=False, validator=instance_of(bool)) script = attr.ib(default="", validator=instance_of(str)) @@ -480,7 +480,7 @@ class PercentilesMetricAgg(object): hide = attr.ib(default=False, validator=instance_of(bool)) inline = attr.ib(default="", validator=instance_of(str)) percents = attr.ib(default=attr.Factory(list)) - settings = attr.ib(default={}) + settings = attr.ib(factory=dict) def to_json_data(self): self.settings = {} From a2fa824001bec21aa252057b6c26bd14106e1ab8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 16:28:59 +0100 Subject: [PATCH 378/403] Bump actions/setup-python from 4.6.0 to 4.6.1 (#599) Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4.6.0 to 4.6.1. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4.6.0...v4.6.1) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-sphinx-and-links.yml | 2 +- .github/workflows/publish-to-pypi.yml | 2 +- .github/workflows/run-tests.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-sphinx-and-links.yml b/.github/workflows/check-sphinx-and-links.yml index 73641eee..68d12c7c 100644 --- a/.github/workflows/check-sphinx-and-links.yml +++ b/.github/workflows/check-sphinx-and-links.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v4.6.0 + uses: actions/setup-python@v4.6.1 with: python-version: 3.8 diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index 26ed2b86..5da24847 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python 3.7 - uses: actions/setup-python@v4.6.0 + uses: actions/setup-python@v4.6.1 with: python-version: 3.7 - name: Build a binary wheel and a source tarball diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 54e98f15..61314c17 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v4.6.0 + uses: actions/setup-python@v4.6.1 with: python-version: ${{ matrix.python }} - name: Run tests From 407a34ce5725a99b89e75fd4a148c5b5ccec745f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Jun 2023 16:29:18 +0100 Subject: [PATCH 379/403] Bump sphinx-rtd-theme from 1.2.0 to 1.2.2 (#603) Bumps [sphinx-rtd-theme](https://github.com/readthedocs/sphinx_rtd_theme) from 1.2.0 to 1.2.2. - [Changelog](https://github.com/readthedocs/sphinx_rtd_theme/blob/master/docs/changelog.rst) - [Commits](https://github.com/readthedocs/sphinx_rtd_theme/compare/1.2.0...1.2.2) --- updated-dependencies: - dependency-name: sphinx-rtd-theme dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index e7edf286..a3f75dea 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ sphinx == 6.1.3 -sphinx_rtd_theme == 1.2.0 \ No newline at end of file +sphinx_rtd_theme == 1.2.2 \ No newline at end of file From 6bef3889f26deb23484c50d0e50c1db66de56525 Mon Sep 17 00:00:00 2001 From: David Worth Date: Tue, 23 Oct 2018 14:39:03 -0600 Subject: [PATCH 380/403] add `hide` attribute to Target --- grafanalib/core.py | 4 +++- grafanalib/tests/test_grafanalib.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index ed109763..cbbbe052 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -581,7 +581,8 @@ class Target(object): step = attr.ib(default=DEFAULT_STEP) target = attr.ib(default="") instant = attr.ib(validator=instance_of(bool), default=False) - datasource = attr.ib(default=None) + datasource = attr.ib(default="") + hide = attr.ib(default=False) def to_json_data(self): return { @@ -598,6 +599,7 @@ def to_json_data(self): 'step': self.step, 'instant': self.instant, 'datasource': self.datasource, + 'hide': self.hide, } diff --git a/grafanalib/tests/test_grafanalib.py b/grafanalib/tests/test_grafanalib.py index 2ca63ef9..57b301d6 100644 --- a/grafanalib/tests/test_grafanalib.py +++ b/grafanalib/tests/test_grafanalib.py @@ -50,6 +50,12 @@ def test_auto_id(): legendFormat='{{namespace}}', refId='A', ), + G.Target( + expr='hidden whatever', + legendFormat='{{namespace}}', + refId='B', + hide=True + ), ], yAxes=G.YAxes( G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds"), From 357ef7609880de64b7883f0233f67c38690ccaae Mon Sep 17 00:00:00 2001 From: David Worth Date: Wed, 24 Oct 2018 10:47:34 -0600 Subject: [PATCH 381/403] add packets/sec (pps) format constant --- grafanalib/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index cbbbe052..148d361b 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -154,6 +154,7 @@ def to_json_data(self): LITRE_FORMAT = 'litre' PERCENT_FORMAT = 'percent' VOLT_AMPERE_FORMAT = 'voltamp' +PACKETS_PER_SEC_FORMAT = "pps" # Alert rule state STATE_NO_DATA = 'no_data' From cb6315c1ce625ad991b1c9950ddaa56909a9f9a6 Mon Sep 17 00:00:00 2001 From: David Worth Date: Wed, 24 Oct 2018 14:47:40 -0600 Subject: [PATCH 382/403] add auto_ref_ids to Graph This makes adding refIDs to targets when they aren't explicitly required much simpler --- grafanalib/core.py | 25 +++++++++++++++++- grafanalib/tests/test_grafanalib.py | 40 +++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 148d361b..0718bc85 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -8,7 +8,7 @@ import itertools import math - +from numbers import Number import string import warnings from numbers import Number @@ -2182,6 +2182,29 @@ def set_refid(t): return self._map_targets(set_refid) + def _iter_targets(self): + for target in self.targets: + yield target + + def _map_targets(self, f): + return attr.assoc(self, targets=[f(t) for t in self.targets]) + + def auto_ref_ids(self): + """Give unique IDs all the panels without IDs. + + Returns a new ``Graph`` that is the same as this one, except all + of the metrics have their ``refId`` property set. Any panels which had an + ``refId`` property set will keep that property, all others will have + auto-generated IDs provided for them. + """ + ref_ids = set([target.refId for target in self._iter_targets() if target.refId]) + candidate_ref_ids = itertools.chain(string.ascii_uppercase, itertools.product(string.ascii_uppercase, repeat=2)) + auto_ref_ids = (i for i in candidate_ref_ids if i not in ref_ids) + + def set_refid(target): + return target if target.refId else attr.assoc(target, refId=next(auto_ref_ids)) + return self._map_targets(set_refid) + @attr.s class TimeSeries(Panel): diff --git a/grafanalib/tests/test_grafanalib.py b/grafanalib/tests/test_grafanalib.py index 57b301d6..914d7bfc 100644 --- a/grafanalib/tests/test_grafanalib.py +++ b/grafanalib/tests/test_grafanalib.py @@ -46,9 +46,8 @@ def test_auto_id(): dataSource="My data source", targets=[ G.Target( - expr='whatever', + expr='whatever #B', legendFormat='{{namespace}}', - refId='A', ), G.Target( expr='hidden whatever', @@ -182,6 +181,43 @@ def test_auto_refids(): assert dashboard.rows[0].panels[0].targets[52].refId == 'BA' +def test_auto_refids(): + """auto_ref_ids() provides refIds for all targets without refIds already set.""" + dashboard = G.Dashboard( + title="Test dashboard", + rows=[ + G.Row(panels=[ + G.Graph( + title="CPU Usage by Namespace (rate[5m])", + dataSource="My data source", + targets=[ + G.Target( + expr='whatever #Q', + legendFormat='{{namespace}}', + ), + G.Target( + expr='hidden whatever', + legendFormat='{{namespace}}', + refId='Q', + hide=True + ), + G.Target( + expr='another target' + ), + ], + yAxes=[ + G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds"), + G.YAxis(format=G.SHORT_FORMAT), + ], + ).auto_ref_ids() + ]), + ], + ) + assert dashboard.rows[0].panels[0].targets[0].refId == 'A' + assert dashboard.rows[0].panels[0].targets[1].refId == 'Q' + assert dashboard.rows[0].panels[0].targets[2].refId == 'B' + + def test_row_show_title(): row = G.Row().to_json_data() assert row['title'] == 'New row' From 6bf6df91c831e3bfbe647ed3d34066081b8a7196 Mon Sep 17 00:00:00 2001 From: Liam Stewart Date: Thu, 13 Dec 2018 16:06:22 -0600 Subject: [PATCH 383/403] Target for cloudwatch datasources. (#1) --- grafanalib/core.py | 33 ++++++++++++++++++++ grafanalib/tests/test_core.py | 57 +++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index 0718bc85..4e14cd0d 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -638,6 +638,39 @@ def to_json_data(self): super_json["rawQuery"] = self.rawQuery return super_json +class CloudWatchTarget(object): + region = attr.ib(default="") + namespace = attr.ib(default="") + metricName = attr.ib(default="") + statistics = attr.ib(default=attr.Factory(list)) + dimensions = attr.ib(default=attr.Factory(dict)) + id = attr.ib(default="") + expression = attr.ib(default="") + period = attr.ib(default="") + alias = attr.ib(default="") + highResolution = attr.ib(default=False, validator=instance_of(bool)) + + refId = attr.ib(default="") + datasource = attr.ib(default="") + hide = attr.ib(default=False, validator=instance_of(bool)) + + def to_json_data(self): + return { + 'region': self.region, + 'namespace': self.namespace, + 'metricName': self.metricName, + 'statistics': self.statistics, + 'dimensions': self.dimensions, + 'id': self.id, + 'expression': self.expression, + 'period': self.period, + 'alias': self.alias, + 'highResolution': self.highResolution, + 'refId': self.refId, + 'datasource': self.datasource, + 'hide': self.hide, + } + @attr.s class Tooltip(object): diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 2b03610b..1714fccb 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -1,6 +1,7 @@ """Tests for core.""" import random + import grafanalib.core as G import pytest @@ -1190,3 +1191,59 @@ def test_sql_target_with_source_files(): assert t.to_json_data()["targets"][0].rawQuery is True assert t.to_json_data()["targets"][0].rawSql == "SELECT example\nFROM test\nWHERE example='example' AND example_date BETWEEN '1970-01-01' AND '1971-01-01';\n" print(t.to_json_data()["targets"][0]) + + +CW_TESTDATA = [ + pytest.param( + {}, + {'region': '', + 'namespace': '', + 'metricName': '', + 'statistics': [], + 'dimensions': {}, + 'id': '', + 'expression': '', + 'period': '', + 'alias': '', + 'highResolution': False, + 'refId': '', + 'datasource': '', + 'hide': False}, + id='defaults', + ), + pytest.param( + {'region': 'us-east-1', + 'namespace': 'AWS/RDS', + 'metricName': 'CPUUtilization', + 'statistics': ['Average'], + 'dimensions': {'DBInstanceIdentifier': 'foo'}, + 'id': 'id', + 'expression': 'expr', + 'period': 'period', + 'alias': 'alias', + 'highResolution': True, + 'refId': 'A', + 'datasource': 'CloudWatch', + 'hide': True, + }, + {'region': 'us-east-1', + 'namespace': 'AWS/RDS', + 'metricName': 'CPUUtilization', + 'statistics': ['Average'], + 'dimensions': {'DBInstanceIdentifier': 'foo'}, + 'id': 'id', + 'expression': 'expr', + 'period': 'period', + 'alias': 'alias', + 'highResolution': True, + 'refId': 'A', + 'datasource': 'CloudWatch', + 'hide': True, + }, + id='custom', + ) +] + +@pytest.mark.parametrize("attrs,expected", CW_TESTDATA) +def test_cloud_watch_target_json_data(attrs, expected): + assert G.CloudWatchTarget(**attrs).to_json_data() == expected From a9bcc92c2d77a168f7e30d36e317c9c3f7b22b9f Mon Sep 17 00:00:00 2001 From: Sara Shi Date: Tue, 15 Jan 2019 16:22:15 -0800 Subject: [PATCH 384/403] Add Ajax Panel object support --- grafanalib/core.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index 4e14cd0d..d10ad67a 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -100,6 +100,7 @@ def to_json_data(self): HISTOGRAM_TYPE = 'histogram' AE3E_PLOTLY_TYPE = 'ae3e-plotly-panel' BAR_CHART_TYPE = 'barchart' +AJAX_TYPE = 'ryantxu-ajax-panel' DEFAULT_FILL = 1 DEFAULT_REFRESH = '10s' @@ -2470,6 +2471,55 @@ def to_json_data(self): 'text': self.text, } +@attr.s +class AjaxPanel(object): + """Generates the Ajax Plugin Panel.""" + method = attr.ib() + params_js = attr.ib() + title = attr.ib() + url = attr.ib() + datasource = attr.ib(default=None) + header_js = attr.ib(default="{}") + id = attr.ib(default=None) + minSpan = attr.ib(default=None) + mode = attr.ib(default=TEXT_MODE_HTML) + responseType = attr.ib(default=TEXT_MODE_TEXT) + skipSameURL = attr.ib(default=True, validator=instance_of(bool)) + showTime = attr.ib(default=False, validator=instance_of(bool)) + showTimeFormat = attr.ib(default="LTS") + showTimePrefix = attr.ib(default=None) + showTimeValue = attr.ib(default="request") + span = attr.ib(default=None) + targets = attr.ib(default=[{}]) + templateResponse = attr.ib(default=True, validator=instance_of(bool)) + transparent = attr.ib(default=False, validator=instance_of(bool)) + type = attr.ib(default=AJAX_TYPE) + withCredentials = attr.ib(default=False, validator=instance_of(bool)) + + def to_json_data(self): + return { + 'method': self.method, + 'params_js': self.params_js, + 'title': self.title, + 'url': self.url, + 'datasource': self.datasource, + 'header_js': self.header_js, + 'id': self.id, + 'minSpan': self.minSpan, + 'mode': self.mode, + 'responseType': self.responseType, + 'skipSameURL': self.skipSameURL, + 'showTime': self.showTime, + 'showTimeFormat': self.showTimeFormat, + 'showTimePrefix': self.showTimePrefix, + 'showTimeValue': self.showTimeValue, + 'span': self.span, + 'targets': self.targets, + 'templateResponse': self.templateResponse, + 'transparent': self.transparent, + 'type': self.type, + 'withCredentials': self.withCredentials + } @attr.s class DiscreteColorMappingItem(object): From 6d47e28c5f31734bc72c1ad3062ca482d35670f0 Mon Sep 17 00:00:00 2001 From: Lulu Ye Date: Fri, 8 Feb 2019 11:02:00 -0700 Subject: [PATCH 385/403] Version bump --- CHANGELOG.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7c480404..fb801158 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -302,6 +302,18 @@ Changes ------- * Minor markup tweaks to the README +0.5.4 (2019-02-08) +================== + +Fixes +----- +* Added more base units + + +New features +------------ +* Added an AJAX panel plugin object + 0.5.2 (2018-07-19) ================== From 466457252c9e71bea6b83feaa4a96174e020512b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=B5=D0=BD=D1=83=D1=81=20=D0=90=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=81=D0=B5=D0=B9=20=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B5?= =?UTF-8?q?=D0=B2=D0=B8=D1=87?= Date: Fri, 14 Sep 2018 18:38:29 +0300 Subject: [PATCH 386/403] Add support for custom variables Version bump to 0.5.5 and update changelog --- CHANGELOG.rst | 8 ++++++++ grafanalib/core.py | 26 ++++++++++++++++++++++++++ grafanalib/tests/test_core.py | 14 ++++++++++++++ 3 files changed, 48 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fb801158..cd6c2054 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -302,6 +302,14 @@ Changes ------- * Minor markup tweaks to the README +0.5.5 (2019-02-13) +================== + +Fixes +----- +* Merged in https://github.com/weaveworks/grafanalib/pull/143 to support custom variables + + 0.5.4 (2019-02-08) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index d10ad67a..d4b5a449 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1050,6 +1050,32 @@ def __attrs_post_init__(self): 'tags': [], } + def __attrs_post_init__(self): + if self.type == 'custom': + if len(self.options) == 0: + for value in self.query.split(','): + is_default = value == self.default + option = { + "selected": is_default, + "text": value, + "value": value, + } + if is_default: + self._current = option + self.options.append(option) + else: + for option in self.options: + if option['selected']: + self._current = option + break + else: + self._current = { + 'text': self.default, + 'value': self.default, + 'tags': [], + } + + def to_json_data(self): return { 'allValue': self.allValue, diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 1714fccb..38391b53 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -97,6 +97,20 @@ def test_custom_template_dont_override_options(): assert t.to_json_data()['current']['value'] == '1' +def test_table_styled_columns(): + t = G.Table.with_styled_columns( + columns=[ + (G.Column('Foo', 'foo'), G.ColumnStyle()), + (G.Column('Bar', 'bar'), None), + ], + type='custom', + ) + + assert len(t.to_json_data()['options']) == 3 + assert t.to_json_data()['current']['text'] == 'some text 1' + assert t.to_json_data()['current']['value'] == '1' + + def test_table(): t = G.Table( dataSource='some data source', From f646933eab13ed4477191567f0905e8862a93bca Mon Sep 17 00:00:00 2001 From: Lulu Ye Date: Thu, 21 Feb 2019 10:20:43 -0700 Subject: [PATCH 387/403] default row.title attribute set to '' --- CHANGELOG.rst | 8 ++++++++ grafanalib/core.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cd6c2054..c46e0ea8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -302,6 +302,14 @@ Changes ------- * Minor markup tweaks to the README +0.5.6 (2019-02-21) +================== + +Fixes +----- +* Set row.title to "" instead of None to get row behavior. + + 0.5.5 (2019-02-13) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index d4b5a449..b2c61c84 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -849,6 +849,41 @@ def to_json_data(self): 'y': self.y } +@attr.s +class Row(object): + # TODO: jml would like to separate the balancing behaviour from this + # layer. + panels = attr.ib(default=attr.Factory(list), convert=_balance_panels) + collapse = attr.ib( + default=False, validator=instance_of(bool), + ) + editable = attr.ib( + default=True, validator=instance_of(bool), + ) + height = attr.ib( + default=attr.Factory(lambda: DEFAULT_ROW_HEIGHT), + validator=instance_of(Pixels), + ) + showTitle = attr.ib(default=None) + title = attr.ib(default="") + repeat = attr.ib(default=None) + + def _iter_panels(self): + return iter(self.panels) + + h = attr.ib() + w = attr.ib() + x = attr.ib() + y = attr.ib() + + def to_json_data(self): + return { + 'h': self.h, + 'w': self.w, + 'x': self.x, + 'y': self.y + } + @attr.s class Annotations(object): From e0eb96063ae2a600ec60ec97389a4cce98bcd383 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 1 Apr 2019 10:36:19 -0700 Subject: [PATCH 388/403] Add Annotation model --- grafanalib/core.py | 110 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index b2c61c84..45b65697 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -4630,3 +4630,113 @@ def to_json_data(self): } ) return bar_chart + + +@attr.s +class Annotation(object): + """ + Annotation create a new dashboard annotation. Annotations can be defined to query a + datasource and overlay information related to key events, such as container exits or deploys. + + :param default: the default value for the variable + :param dataSource: where to fetch the values for the variable from + :param label: the variable's human label + :param name: the variable's name + :param expr: the query users to fetch the valid values of the variable + :param step: the time window size to use when querying for annotation data + :param refresh: Controls when to update values in the dropdown + :param allValue: specify a custom all value with regex, + globs or lucene syntax. + :param includeAll: Add a special All option whose value includes + all options. + :param regex: Regex to filter or capture specific parts of the names + return by your data source query. + :param multi: If enabled, the variable will support the selection of + multiple options at the same time. + :param type: The template type, can be one of: query (default), + interval, datasource, custom, constant, adhoc. + :param hide: Hide this variable in the dashboard, can be one of: + SHOW (default), HIDE_LABEL, HIDE_VARIABLE + """ + name = attr.ib() + expr = attr.ib() + _current = attr.ib(init=False, default=attr.Factory(dict)) + default = attr.ib(default=None) + dataSource = attr.ib(default=None) + label = attr.ib(default=None) + allValue = attr.ib(default=None) + hide = attr.ib(default=SHOW) + iconColor = attr.ib(default=None) + enable = attr.ib( + default=True, + validator=instance_of(bool), + ) + includeAll = attr.ib( + default=False, + validator=instance_of(bool), + ) + multi = attr.ib( + default=False, + validator=instance_of(bool), + ) + options = attr.ib(default=attr.Factory(list)) + refresh = attr.ib(default=REFRESH_ON_DASHBOARD_LOAD, + validator=instance_of(int)) + regex = attr.ib(default=None) + step = attr.ib(default=None) + tagsQuery = attr.ib(default=None) + tagValuesQuery = attr.ib(default=None) + type = attr.ib(default='query') + useTags = attr.ib( + default=False, + validator=instance_of(bool), + ) + + def __attrs_post_init__(self): + if self.type == 'custom': + if len(self.options) == 0: + for value in self.query.split(','): + is_default = value == self.default + option = { + "selected": is_default, + "text": value, + "value": value, + } + if is_default: + self._current = option + self.options.append(option) + else: + for option in self.options: + if option['selected']: + self._current = option + break + else: + self._current = { + 'text': self.default, + 'value': self.default, + 'tags': [], + } + + def to_json_data(self): + return { + 'allValue': self.allValue, + 'current': self._current, + 'datasource': self.dataSource, + 'enable': self.enable, + 'expr': self.expr, + 'hide': self.hide, + 'iconColor': self.iconColor, + 'includeAll': self.includeAll, + 'label': self.label, + 'multi': self.multi, + 'name': self.name, + 'options': self.options, + 'refresh': self.refresh, + 'regex': self.regex, + 'sort': 1, + 'step': self.step, + 'type': self.type, + 'useTags': self.useTags, + 'tagsQuery': self.tagsQuery, + 'tagValuesQuery': self.tagValuesQuery, + } From e3ab26af4d4b16530e5fc9eb3ad3be2d7977e41f Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 1 Apr 2019 10:45:51 -0700 Subject: [PATCH 389/403] Add textFormat field --- grafanalib/core.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/grafanalib/core.py b/grafanalib/core.py index 45b65697..06bf6ee5 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -4644,6 +4644,7 @@ class Annotation(object): :param name: the variable's name :param expr: the query users to fetch the valid values of the variable :param step: the time window size to use when querying for annotation data + :param textFormat: text to display when hovering over annotation :param refresh: Controls when to update values in the dropdown :param allValue: specify a custom all value with regex, globs or lucene syntax. @@ -4686,6 +4687,7 @@ class Annotation(object): step = attr.ib(default=None) tagsQuery = attr.ib(default=None) tagValuesQuery = attr.ib(default=None) + textFormat = attr.ib(default=None) type = attr.ib(default='query') useTags = attr.ib( default=False, @@ -4735,6 +4737,7 @@ def to_json_data(self): 'regex': self.regex, 'sort': 1, 'step': self.step, + 'textFormat': self.textFormat, 'type': self.type, 'useTags': self.useTags, 'tagsQuery': self.tagsQuery, From b607ba9262480c1aecbb65acff578462642fad40 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 1 Apr 2019 14:08:34 -0700 Subject: [PATCH 390/403] Add target and rename datasource --- grafanalib/core.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 06bf6ee5..6b8f1281 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -4635,11 +4635,11 @@ def to_json_data(self): @attr.s class Annotation(object): """ - Annotation create a new dashboard annotation. Annotations can be defined to query a + Annotation creates a new dashboard annotation. Annotations can be defined to query a datasource and overlay information related to key events, such as container exits or deploys. :param default: the default value for the variable - :param dataSource: where to fetch the values for the variable from + :param datasource: where to fetch the values for the variable from :param label: the variable's human label :param name: the variable's name :param expr: the query users to fetch the valid values of the variable @@ -4660,10 +4660,11 @@ class Annotation(object): SHOW (default), HIDE_LABEL, HIDE_VARIABLE """ name = attr.ib() - expr = attr.ib() + expr = attr.ib(default=None) + target = attr.ib(default=None) _current = attr.ib(init=False, default=attr.Factory(dict)) default = attr.ib(default=None) - dataSource = attr.ib(default=None) + datasource = attr.ib(default=None) label = attr.ib(default=None) allValue = attr.ib(default=None) hide = attr.ib(default=SHOW) @@ -4723,7 +4724,7 @@ def to_json_data(self): return { 'allValue': self.allValue, 'current': self._current, - 'datasource': self.dataSource, + 'datasource': self.datasource, 'enable': self.enable, 'expr': self.expr, 'hide': self.hide, @@ -4737,6 +4738,7 @@ def to_json_data(self): 'regex': self.regex, 'sort': 1, 'step': self.step, + 'target': self.target, 'textFormat': self.textFormat, 'type': self.type, 'useTags': self.useTags, From 38d4aee66ec52583811905ef773f97dc235791f2 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 1 Apr 2019 14:11:33 -0700 Subject: [PATCH 391/403] Remove init function --- grafanalib/core.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 6b8f1281..22b2a1f4 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -4695,31 +4695,6 @@ class Annotation(object): validator=instance_of(bool), ) - def __attrs_post_init__(self): - if self.type == 'custom': - if len(self.options) == 0: - for value in self.query.split(','): - is_default = value == self.default - option = { - "selected": is_default, - "text": value, - "value": value, - } - if is_default: - self._current = option - self.options.append(option) - else: - for option in self.options: - if option['selected']: - self._current = option - break - else: - self._current = { - 'text': self.default, - 'value': self.default, - 'tags': [], - } - def to_json_data(self): return { 'allValue': self.allValue, From df8362d5a8ae82b9526b4918ece6960757eda82b Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Mon, 1 Apr 2019 14:20:08 -0700 Subject: [PATCH 392/403] Update version and changelog --- CHANGELOG.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c46e0ea8..65b2fd99 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -302,6 +302,14 @@ Changes ------- * Minor markup tweaks to the README +0.5.7 (2019-04-01) +================== + +Fixes +----- +* Add `Annotation` model for defining dashboard annotations + + 0.5.6 (2019-02-21) ================== From 3c74b8f589ad1d6f0a3549e73056e295bd97c062 Mon Sep 17 00:00:00 2001 From: Lulu Ye Date: Mon, 11 Mar 2019 15:38:43 -0600 Subject: [PATCH 393/403] Add decimals field to Graph class --- CHANGELOG.rst | 10 +++++++++- grafanalib/core.py | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 65b2fd99..5952c776 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -302,6 +302,15 @@ Changes ------- * Minor markup tweaks to the README +0.5.8 (2019-04-09) +================== + +Fixes +----- + +* Add 'decimals' field to the Graph class to dictate significant digits in Grafana legend + + 0.5.7 (2019-04-01) ================== @@ -309,7 +318,6 @@ Fixes ----- * Add `Annotation` model for defining dashboard annotations - 0.5.6 (2019-02-21) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 22b2a1f4..c7d88eb5 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1962,6 +1962,7 @@ class Panel(object): targets = attr.ib(default=attr.Factory(list), validator=instance_of(list)) title = attr.ib(default="") cacheTimeout = attr.ib(default=None) + decimals = attr.ib(default=1) description = attr.ib(default=None) editable = attr.ib(default=True, validator=instance_of(bool)) error = attr.ib(default=False, validator=instance_of(bool)) @@ -1990,6 +1991,7 @@ def panel_json(self, overrides): res = { 'cacheTimeout': self.cacheTimeout, 'datasource': self.dataSource, + 'decimals': self.decimals, 'description': self.description, 'editable': self.editable, 'error': self.error, From e8fd0452cce5f59d5341983e424560a229dfa557 Mon Sep 17 00:00:00 2001 From: Jason Milliron Date: Mon, 20 May 2019 18:40:58 -0700 Subject: [PATCH 394/403] [INFRA-3137] change Tooltip valueType defult to INDIVIDUAL --- grafanalib/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index c7d88eb5..8dc219c5 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -679,7 +679,7 @@ class Tooltip(object): msResolution = attr.ib(default=True, validator=instance_of(bool)) shared = attr.ib(default=True, validator=instance_of(bool)) sort = attr.ib(default=0) - valueType = attr.ib(default=CUMULATIVE) + valueType = attr.ib(default=INDIVIDUAL) def to_json_data(self): return { From 4a036f1eeebf87be044d3addf3a30961cad323f4 Mon Sep 17 00:00:00 2001 From: Jason Milliron Date: Tue, 21 May 2019 10:43:56 -0700 Subject: [PATCH 395/403] fixup --- CHANGELOG.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5952c776..93207f9c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -302,6 +302,15 @@ Changes ------- * Minor markup tweaks to the README +0.5.9 (2019-05-12) +================== + +Fixes +----- + +* Change Tooltip valueType default from CUMULATIVE to INDIVIDUAL for stacked graphs. + + 0.5.8 (2019-04-09) ================== From 468d160c5a084864f4e05a11b6de2be567e19d86 Mon Sep 17 00:00:00 2001 From: Jason Milliron Date: Tue, 21 May 2019 10:44:24 -0700 Subject: [PATCH 396/403] fixup --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 93207f9c..4a579ab4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -303,6 +303,7 @@ Changes * Minor markup tweaks to the README 0.5.9 (2019-05-12) +0.5.9 (2019-05-21) ================== Fixes From 64e617212698e642dd5932c6ae10eb34077ef51d Mon Sep 17 00:00:00 2001 From: David Worth Date: Fri, 13 Mar 2020 17:01:28 -0600 Subject: [PATCH 397/403] coverage: let's not measure coverage on tests --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.coveragerc b/.coveragerc index 42987ac8..19f17499 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,3 +3,5 @@ branch = True include = grafanalib/*.py grafanalib/**/*.py +omit = + grafanalib/tests/*.py From b34c9d3d8ddc1a8eb62ecc593c0b8c98bddaff80 Mon Sep 17 00:00:00 2001 From: David Worth Date: Fri, 13 Mar 2020 17:02:02 -0600 Subject: [PATCH 398/403] make tests pass --- grafanalib/core.py | 12 ++++++++++-- setup.py | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 8dc219c5..135546a9 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -853,7 +853,7 @@ def to_json_data(self): class Row(object): # TODO: jml would like to separate the balancing behaviour from this # layer. - panels = attr.ib(default=attr.Factory(list), convert=_balance_panels) + panels = attr.ib(default=attr.Factory(list), converter=_balance_panels) collapse = attr.ib( default=False, validator=instance_of(bool), ) @@ -865,7 +865,7 @@ class Row(object): validator=instance_of(Pixels), ) showTitle = attr.ib(default=None) - title = attr.ib(default="") + title = attr.ib(default=None) repeat = attr.ib(default=None) def _iter_panels(self): @@ -1983,6 +1983,14 @@ class Panel(object): transparent = attr.ib(default=False, validator=instance_of(bool)) transformations = attr.ib(default=attr.Factory(list), validator=instance_of(list)) extraJson = attr.ib(default=None, validator=attr.validators.optional(instance_of(dict))) + xAxis = attr.ib(default=attr.Factory(XAxis), validator=instance_of(XAxis)) + # XXX: This isn't a *good* default, rather it's the default Grafana uses. + yAxes = attr.ib( + default=attr.Factory(YAxes), + converter=to_y_axes, + validator=instance_of(YAxes), + ) + alert = attr.ib(default=None) def _map_panels(self, f): return f(self) diff --git a/setup.py b/setup.py index 3108e480..0eb10c4e 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,7 @@ def local_file(name): 'Topic :: System :: Monitoring', ], install_requires=[ - 'attrs>=15.2.0', + 'attrs>=19.3.0', ], extras_require={ 'dev': [ From f050218d02b7d256483af5c7fe199e961d5fbc00 Mon Sep 17 00:00:00 2001 From: David Worth Date: Tue, 14 Apr 2020 09:31:07 -0600 Subject: [PATCH 399/403] set default dashboard refresh time to 1m This will help prevent overloading metric sources when many panels are being refreshed constantly, particularly when many teammates are viewing the same panels during incident response. --- CHANGELOG.rst | 5 +++++ grafanalib/core.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4a579ab4..67cc9b27 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -303,6 +303,11 @@ Changes * Minor markup tweaks to the README 0.5.9 (2019-05-12) +0.5.10 (2020-04-14) +=================== + +* Set default refresh to 1m instead of 10s to reduce load on metric sources. + 0.5.9 (2019-05-21) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 135546a9..92566894 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -103,7 +103,7 @@ def to_json_data(self): AJAX_TYPE = 'ryantxu-ajax-panel' DEFAULT_FILL = 1 -DEFAULT_REFRESH = '10s' +DEFAULT_REFRESH = '1m' DEFAULT_ALERT_EVALUATE_INTERVAL = '1m' DEFAULT_ALERT_EVALUATE_FOR = '5m' DEFAULT_ROW_HEIGHT = Pixels(250) From aa35dc6cfede733f055ed273fb6e9431b67b7fba Mon Sep 17 00:00:00 2001 From: David Worth Date: Tue, 14 Apr 2020 09:31:27 -0600 Subject: [PATCH 400/403] whitespace and flake8 updates to make the lint target pass --- grafanalib/core.py | 70 +++++++++++++++++++---------- grafanalib/tests/test_core.py | 55 ++++++++++++----------- grafanalib/tests/test_grafanalib.py | 4 +- 3 files changed, 78 insertions(+), 51 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 92566894..995896e0 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -2035,6 +2035,7 @@ def panel_json(self, overrides): return res +<<<<<<< HEAD @attr.s class ePict(Panel): """ @@ -2045,6 +2046,24 @@ class ePict(Panel): :param bgURL: Where to load the image from. :param boxes: The info boxes to be placed on the image. """ +======= + Returns a new ``Graph`` that is the same as this one, except all of + the metrics have their ``refId`` property set. Any panels which had + an ``refId`` property set will keep that property, all others will + have auto-generated IDs provided for them. + """ + ref_ids = set( + [target.refId for target in self._iter_targets() if target.refId]) + candidate_ref_ids = itertools.chain( + string.ascii_uppercase, + itertools.product(string.ascii_uppercase, repeat=2) + ) + auto_ref_ids = (i for i in candidate_ref_ids if i not in ref_ids) + + def set_refid(target): + return target if target.refId else attr.assoc(target, refId=next(auto_ref_ids)) # noqa: E501 + return self._map_targets(set_refid) +>>>>>>> 014b0c9 (whitespace and flake8 updates to make the lint target pass) bgURL = attr.ib(default='', validator=instance_of(str)) @@ -2542,6 +2561,7 @@ def to_json_data(self): 'text': self.text, } + @attr.s class AjaxPanel(object): """Generates the Ajax Plugin Panel.""" @@ -2569,29 +2589,30 @@ class AjaxPanel(object): def to_json_data(self): return { - 'method': self.method, - 'params_js': self.params_js, - 'title': self.title, - 'url': self.url, - 'datasource': self.datasource, - 'header_js': self.header_js, - 'id': self.id, - 'minSpan': self.minSpan, - 'mode': self.mode, - 'responseType': self.responseType, - 'skipSameURL': self.skipSameURL, - 'showTime': self.showTime, - 'showTimeFormat': self.showTimeFormat, - 'showTimePrefix': self.showTimePrefix, - 'showTimeValue': self.showTimeValue, - 'span': self.span, - 'targets': self.targets, - 'templateResponse': self.templateResponse, - 'transparent': self.transparent, - 'type': self.type, - 'withCredentials': self.withCredentials + 'method': self.method, + 'params_js': self.params_js, + 'title': self.title, + 'url': self.url, + 'datasource': self.datasource, + 'header_js': self.header_js, + 'id': self.id, + 'minSpan': self.minSpan, + 'mode': self.mode, + 'responseType': self.responseType, + 'skipSameURL': self.skipSameURL, + 'showTime': self.showTime, + 'showTimeFormat': self.showTimeFormat, + 'showTimePrefix': self.showTimePrefix, + 'showTimeValue': self.showTimeValue, + 'span': self.span, + 'targets': self.targets, + 'templateResponse': self.templateResponse, + 'transparent': self.transparent, + 'type': self.type, + 'withCredentials': self.withCredentials } + @attr.s class DiscreteColorMappingItem(object): """ @@ -4645,8 +4666,9 @@ def to_json_data(self): @attr.s class Annotation(object): """ - Annotation creates a new dashboard annotation. Annotations can be defined to query a - datasource and overlay information related to key events, such as container exits or deploys. + Annotation creates a new dashboard annotation. Annotations can be defined + to query a datasource and overlay information related to key events, such + as container exits or deploys. :param default: the default value for the variable :param datasource: where to fetch the values for the variable from @@ -4668,7 +4690,7 @@ class Annotation(object): interval, datasource, custom, constant, adhoc. :param hide: Hide this variable in the dashboard, can be one of: SHOW (default), HIDE_LABEL, HIDE_VARIABLE - """ + """ # noqa: E501 name = attr.ib() expr = attr.ib(default=None) target = attr.ib(default=None) diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 38391b53..6e382e51 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -1226,38 +1226,41 @@ def test_sql_target_with_source_files(): id='defaults', ), pytest.param( - {'region': 'us-east-1', - 'namespace': 'AWS/RDS', - 'metricName': 'CPUUtilization', - 'statistics': ['Average'], - 'dimensions': {'DBInstanceIdentifier': 'foo'}, - 'id': 'id', - 'expression': 'expr', - 'period': 'period', - 'alias': 'alias', - 'highResolution': True, - 'refId': 'A', - 'datasource': 'CloudWatch', - 'hide': True, + { + 'region': 'us-east-1', + 'namespace': 'AWS/RDS', + 'metricName': 'CPUUtilization', + 'statistics': ['Average'], + 'dimensions': {'DBInstanceIdentifier': 'foo'}, + 'id': 'id', + 'expression': 'expr', + 'period': 'period', + 'alias': 'alias', + 'highResolution': True, + 'refId': 'A', + 'datasource': 'CloudWatch', + 'hide': True, }, - {'region': 'us-east-1', - 'namespace': 'AWS/RDS', - 'metricName': 'CPUUtilization', - 'statistics': ['Average'], - 'dimensions': {'DBInstanceIdentifier': 'foo'}, - 'id': 'id', - 'expression': 'expr', - 'period': 'period', - 'alias': 'alias', - 'highResolution': True, - 'refId': 'A', - 'datasource': 'CloudWatch', - 'hide': True, + { + 'region': 'us-east-1', + 'namespace': 'AWS/RDS', + 'metricName': 'CPUUtilization', + 'statistics': ['Average'], + 'dimensions': {'DBInstanceIdentifier': 'foo'}, + 'id': 'id', + 'expression': 'expr', + 'period': 'period', + 'alias': 'alias', + 'highResolution': True, + 'refId': 'A', + 'datasource': 'CloudWatch', + 'hide': True, }, id='custom', ) ] + @pytest.mark.parametrize("attrs,expected", CW_TESTDATA) def test_cloud_watch_target_json_data(attrs, expected): assert G.CloudWatchTarget(**attrs).to_json_data() == expected diff --git a/grafanalib/tests/test_grafanalib.py b/grafanalib/tests/test_grafanalib.py index 914d7bfc..3a53ef57 100644 --- a/grafanalib/tests/test_grafanalib.py +++ b/grafanalib/tests/test_grafanalib.py @@ -182,7 +182,9 @@ def test_auto_refids(): def test_auto_refids(): - """auto_ref_ids() provides refIds for all targets without refIds already set.""" + """ + auto_ref_ids() provides refIds for all targets without refIds already set. + """ dashboard = G.Dashboard( title="Test dashboard", rows=[ From 9c38ca9e30557b95aa5e467d603431d9c8c9dc46 Mon Sep 17 00:00:00 2001 From: Lulu Ye Date: Thu, 23 Apr 2020 09:55:37 -0600 Subject: [PATCH 401/403] Set sharedCrosshair to true by default. Also fixed changelog and versioning info. --- CHANGELOG.rst | 8 ++++++++ grafanalib/core.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 67cc9b27..583c5a3a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -306,7 +306,15 @@ Changes 0.5.10 (2020-04-14) =================== +0.6.1 (2020-04-23) +================== * Set default refresh to 1m instead of 10s to reduce load on metric sources. +* Set sharedCrosshair to default=true. + + +0.6.0 +=================== +* Fix tests 0.5.9 (2019-05-21) ================== diff --git a/grafanalib/core.py b/grafanalib/core.py index 995896e0..55346d1a 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -1519,10 +1519,54 @@ class AlertGroup(object): :param folder: Folder to hold alert (Grafana 9.x) :param evaluateInterval: Interval at which the group of alerts is to be evaluated """ + """ + # This is from upstream name = attr.ib() rules = attr.ib(default=attr.Factory(list), validator=instance_of(list)) folder = attr.ib(default='alert', validator=instance_of(str)) evaluateInterval = attr.ib(default='1m', validator=instance_of(str)) + """ + title = attr.ib() + rows = attr.ib() + annotations = attr.ib( + default=attr.Factory(Annotations), + validator=instance_of(Annotations), + ) + editable = attr.ib( + default=True, + validator=instance_of(bool), + ) + gnetId = attr.ib(default=None) + hideControls = attr.ib( + default=False, + validator=instance_of(bool), + ) + id = attr.ib(default=None) + inputs = attr.ib(default=attr.Factory(list)) + links = attr.ib(default=attr.Factory(list)) + refresh = attr.ib(default=DEFAULT_REFRESH) + schemaVersion = attr.ib(default=SCHEMA_VERSION) + sharedCrosshair = attr.ib( + default=True, + validator=instance_of(bool), + ) + style = attr.ib(default=DARK_STYLE) + tags = attr.ib(default=attr.Factory(list)) + templating = attr.ib( + default=attr.Factory(Templating), + validator=instance_of(Templating), + ) + time = attr.ib( + default=attr.Factory(lambda: DEFAULT_TIME), + validator=instance_of(Time), + ) + timePicker = attr.ib( + default=attr.Factory(lambda: DEFAULT_TIME_PICKER), + validator=instance_of(TimePicker), + ) + timezone = attr.ib(default=UTC) + version = attr.ib(default=0) + uid = attr.ib(default=None) def group_rules(self, rules): grouped_rules = [] From 188e0023bd62e92ca9aaceffc5f696519fc7feb3 Mon Sep 17 00:00:00 2001 From: Sara Shi Date: Mon, 6 Mar 2023 13:17:41 -0800 Subject: [PATCH 402/403] Add CODEOWNERS file --- CODEOWNERS | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..aaf0dc18 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,6 @@ +# This repo is owned by the Infrastructure team. These +# owners will be the default owners for everything in the +# repo. @infrastructure will be requested for a review when +# someone opens a pull request. Approval from a member of +# the team is mandatory. +* @strava/infrastructure From fe7fcd93f3d53bd0b02be8949142d70a77ec037b Mon Sep 17 00:00:00 2001 From: Paul O'Connor Date: Thu, 29 Jun 2023 16:31:34 +0100 Subject: [PATCH 403/403] Bring our fork up to date with upstream --- grafanalib/core.py | 360 ++++++++++------------------ grafanalib/tests/test_core.py | 74 ------ grafanalib/tests/test_grafanalib.py | 43 +--- 3 files changed, 126 insertions(+), 351 deletions(-) diff --git a/grafanalib/core.py b/grafanalib/core.py index 55346d1a..df52e215 100644 --- a/grafanalib/core.py +++ b/grafanalib/core.py @@ -8,7 +8,6 @@ import itertools import math -from numbers import Number import string import warnings from numbers import Number @@ -601,7 +600,6 @@ def to_json_data(self): 'step': self.step, 'instant': self.instant, 'datasource': self.datasource, - 'hide': self.hide, } @@ -639,39 +637,6 @@ def to_json_data(self): super_json["rawQuery"] = self.rawQuery return super_json -class CloudWatchTarget(object): - region = attr.ib(default="") - namespace = attr.ib(default="") - metricName = attr.ib(default="") - statistics = attr.ib(default=attr.Factory(list)) - dimensions = attr.ib(default=attr.Factory(dict)) - id = attr.ib(default="") - expression = attr.ib(default="") - period = attr.ib(default="") - alias = attr.ib(default="") - highResolution = attr.ib(default=False, validator=instance_of(bool)) - - refId = attr.ib(default="") - datasource = attr.ib(default="") - hide = attr.ib(default=False, validator=instance_of(bool)) - - def to_json_data(self): - return { - 'region': self.region, - 'namespace': self.namespace, - 'metricName': self.metricName, - 'statistics': self.statistics, - 'dimensions': self.dimensions, - 'id': self.id, - 'expression': self.expression, - 'period': self.period, - 'alias': self.alias, - 'highResolution': self.highResolution, - 'refId': self.refId, - 'datasource': self.datasource, - 'hide': self.hide, - } - @attr.s class Tooltip(object): @@ -849,41 +814,6 @@ def to_json_data(self): 'y': self.y } -@attr.s -class Row(object): - # TODO: jml would like to separate the balancing behaviour from this - # layer. - panels = attr.ib(default=attr.Factory(list), converter=_balance_panels) - collapse = attr.ib( - default=False, validator=instance_of(bool), - ) - editable = attr.ib( - default=True, validator=instance_of(bool), - ) - height = attr.ib( - default=attr.Factory(lambda: DEFAULT_ROW_HEIGHT), - validator=instance_of(Pixels), - ) - showTitle = attr.ib(default=None) - title = attr.ib(default=None) - repeat = attr.ib(default=None) - - def _iter_panels(self): - return iter(self.panels) - - h = attr.ib() - w = attr.ib() - x = attr.ib() - y = attr.ib() - - def to_json_data(self): - return { - 'h': self.h, - 'w': self.w, - 'x': self.x, - 'y': self.y - } - @attr.s class Annotations(object): @@ -1085,32 +1015,6 @@ def __attrs_post_init__(self): 'tags': [], } - def __attrs_post_init__(self): - if self.type == 'custom': - if len(self.options) == 0: - for value in self.query.split(','): - is_default = value == self.default - option = { - "selected": is_default, - "text": value, - "value": value, - } - if is_default: - self._current = option - self.options.append(option) - else: - for option in self.options: - if option['selected']: - self._current = option - break - else: - self._current = { - 'text': self.default, - 'value': self.default, - 'tags': [], - } - - def to_json_data(self): return { 'allValue': self.allValue, @@ -1513,60 +1417,15 @@ def to_json_data(self): class AlertGroup(object): """ Create an alert group of Grafana 8.x alerts - :param name: Alert group name :param rules: List of AlertRule :param folder: Folder to hold alert (Grafana 9.x) :param evaluateInterval: Interval at which the group of alerts is to be evaluated """ - """ - # This is from upstream name = attr.ib() rules = attr.ib(default=attr.Factory(list), validator=instance_of(list)) folder = attr.ib(default='alert', validator=instance_of(str)) evaluateInterval = attr.ib(default='1m', validator=instance_of(str)) - """ - title = attr.ib() - rows = attr.ib() - annotations = attr.ib( - default=attr.Factory(Annotations), - validator=instance_of(Annotations), - ) - editable = attr.ib( - default=True, - validator=instance_of(bool), - ) - gnetId = attr.ib(default=None) - hideControls = attr.ib( - default=False, - validator=instance_of(bool), - ) - id = attr.ib(default=None) - inputs = attr.ib(default=attr.Factory(list)) - links = attr.ib(default=attr.Factory(list)) - refresh = attr.ib(default=DEFAULT_REFRESH) - schemaVersion = attr.ib(default=SCHEMA_VERSION) - sharedCrosshair = attr.ib( - default=True, - validator=instance_of(bool), - ) - style = attr.ib(default=DARK_STYLE) - tags = attr.ib(default=attr.Factory(list)) - templating = attr.ib( - default=attr.Factory(Templating), - validator=instance_of(Templating), - ) - time = attr.ib( - default=attr.Factory(lambda: DEFAULT_TIME), - validator=instance_of(Time), - ) - timePicker = attr.ib( - default=attr.Factory(lambda: DEFAULT_TIME_PICKER), - validator=instance_of(TimePicker), - ) - timezone = attr.ib(default=UTC) - version = attr.ib(default=0) - uid = attr.ib(default=None) def group_rules(self, rules): grouped_rules = [] @@ -1869,7 +1728,7 @@ class Dashboard(object): rows = attr.ib(default=attr.Factory(list), validator=instance_of(list)) schemaVersion = attr.ib(default=SCHEMA_VERSION) sharedCrosshair = attr.ib( - default=False, + default=True, validator=instance_of(bool), ) style = attr.ib(default=DARK_STYLE) @@ -2079,35 +1938,15 @@ def panel_json(self, overrides): return res -<<<<<<< HEAD @attr.s class ePict(Panel): """ Generates ePict panel json structure. https://grafana.com/grafana/plugins/larona-epict-panel/ - :param autoScale: Whether to auto scale image to panel size. :param bgURL: Where to load the image from. :param boxes: The info boxes to be placed on the image. """ -======= - Returns a new ``Graph`` that is the same as this one, except all of - the metrics have their ``refId`` property set. Any panels which had - an ``refId`` property set will keep that property, all others will - have auto-generated IDs provided for them. - """ - ref_ids = set( - [target.refId for target in self._iter_targets() if target.refId]) - candidate_ref_ids = itertools.chain( - string.ascii_uppercase, - itertools.product(string.ascii_uppercase, repeat=2) - ) - auto_ref_ids = (i for i in candidate_ref_ids if i not in ref_ids) - - def set_refid(target): - return target if target.refId else attr.assoc(target, refId=next(auto_ref_ids)) # noqa: E501 - return self._map_targets(set_refid) ->>>>>>> 014b0c9 (whitespace and flake8 updates to make the lint target pass) bgURL = attr.ib(default='', validator=instance_of(str)) @@ -2184,7 +2023,7 @@ class Row(object): validator=instance_of(Pixels), ) showTitle = attr.ib(default=None) - title = attr.ib(default=None) + title = attr.ib(default="") repeat = attr.ib(default=None) def _iter_panels(self): @@ -2350,29 +2189,6 @@ def set_refid(t): return self._map_targets(set_refid) - def _iter_targets(self): - for target in self.targets: - yield target - - def _map_targets(self, f): - return attr.assoc(self, targets=[f(t) for t in self.targets]) - - def auto_ref_ids(self): - """Give unique IDs all the panels without IDs. - - Returns a new ``Graph`` that is the same as this one, except all - of the metrics have their ``refId`` property set. Any panels which had an - ``refId`` property set will keep that property, all others will have - auto-generated IDs provided for them. - """ - ref_ids = set([target.refId for target in self._iter_targets() if target.refId]) - candidate_ref_ids = itertools.chain(string.ascii_uppercase, itertools.product(string.ascii_uppercase, repeat=2)) - auto_ref_ids = (i for i in candidate_ref_ids if i not in ref_ids) - - def set_refid(target): - return target if target.refId else attr.assoc(target, refId=next(auto_ref_ids)) - return self._map_targets(set_refid) - @attr.s class TimeSeries(Panel): @@ -3422,70 +3238,142 @@ def to_json_data(self): } +def _style_columns(columns): + """Generate a list of column styles given some styled columns. + + The 'Table' object in Grafana separates column definitions from column + style definitions. However, when defining dashboards it can be very useful + to define the style next to the column. This function helps that happen. + + :param columns: A list of (Column, ColumnStyle) pairs. The associated + ColumnStyles must not have a 'pattern' specified. You can also provide + 'None' if you want to use the default styles. + :return: A list of ColumnStyle values that can be used in a Grafana + definition. + """ + new_columns = [] + styles = [] + for column, style in columns: + new_columns.append(column) + if not style: + continue + if style.pattern and style.pattern != column.text: + raise ValueError( + "ColumnStyle pattern (%r) must match the column name (%r) if " + "specified" % (style.pattern, column.text)) + styles.append(attr.evolve(style, pattern=column.text)) + return new_columns, styles + + @attr.s -class Table(Panel): +class Table(object): """Generates Table panel json structure - Now supports Grafana v8+ - Grafana doc on table: https://grafana.com/docs/grafana/latest/visualizations/table/ + Grafana doc on table: http://docs.grafana.org/reference/table_panel/ - :param align: Align cell contents; auto (default), left, center, right - :param colorMode: Default thresholds - :param columns: Table columns for Aggregations view - :param displayMode: By default, Grafana automatically chooses display settings, you can choose; - color-text, color-background, color-background-solid, gradient-gauge, lcd-gauge, basic, json-view - :param fontSize: Defines value font size - :param filterable: Allow user to filter columns, default False - :param mappings: To assign colors to boolean or string values, use Value mappings - :param overrides: To override the base characteristics of certain data - :param showHeader: Show the table header + :param columns: table columns for Aggregations view + :param dataSource: Grafana datasource name + :param description: optional panel description + :param editable: defines if panel is editable via web interfaces + :param fontSize: defines value font size + :param height: defines panel height + :param hideTimeOverride: hides time overrides + :param id: panel id + :param links: additional web links + :param minSpan: minimum span number + :param pageSize: rows per page (None is unlimited) + :param scroll: scroll the table instead of displaying in full + :param showHeader: show the table header + :param span: defines the number of spans that will be used for panel + :param styles: defines formatting for each column + :param targets: list of metric requests for chosen datasource + :param timeFrom: time range that Override relative time + :param title: panel title + :param transform: table style + :param transparent: defines if panel should be transparent """ - align = attr.ib(default='auto', validator=instance_of(str)) - colorMode = attr.ib(default='thresholds', validator=instance_of(str)) + dataSource = attr.ib() + targets = attr.ib() + title = attr.ib() columns = attr.ib(default=attr.Factory(list)) - displayMode = attr.ib(default='auto', validator=instance_of(str)) - fontSize = attr.ib(default='100%') - filterable = attr.ib(default=False, validator=instance_of(bool)) - mappings = attr.ib(default=attr.Factory(list)) - overrides = attr.ib(default=attr.Factory(list)) + description = attr.ib(default=None) + editable = attr.ib(default=True, validator=instance_of(bool)) + fontSize = attr.ib(default="100%") + height = attr.ib(default=None) + hideTimeOverride = attr.ib(default=False, validator=instance_of(bool)) + id = attr.ib(default=None) + links = attr.ib(default=attr.Factory(list)) + minSpan = attr.ib(default=None) + pageSize = attr.ib(default=None) + repeat = attr.ib(default=None) + scroll = attr.ib(default=True, validator=instance_of(bool)) showHeader = attr.ib(default=True, validator=instance_of(bool)) span = attr.ib(default=6) + sort = attr.ib( + default=attr.Factory(ColumnSort), validator=instance_of(ColumnSort)) + styles = attr.ib() + timeFrom = attr.ib(default=None) + + transform = attr.ib(default=COLUMNS_TRANSFORM) + transparent = attr.ib(default=False, validator=instance_of(bool)) + + @styles.default + def styles_default(self): + return [ + ColumnStyle( + alias="Time", + pattern="time", + type=DateColumnStyleType(), + ), + ColumnStyle( + pattern="/.*/", + ), + ] @classmethod def with_styled_columns(cls, columns, styles=None, **kwargs): - """Styled columns is not support in Grafana v8 Table""" - print("Error: Styled columns is not support in Grafana v8 Table") - print("Please see https://grafana.com/docs/grafana/latest/visualizations/table/ for more options") - raise NotImplementedError + """Construct a table where each column has an associated style. + + :param columns: A list of (Column, ColumnStyle) pairs, where the + ColumnStyle is the style for the column and does **not** have a + pattern set (or the pattern is set to exactly the column name). + The ColumnStyle may also be None. + :param styles: An optional list of extra column styles that will be + appended to the table's list of styles. + :param **kwargs: Other parameters to the Table constructor. + :return: A Table. + """ + extraStyles = styles if styles else [] + columns, styles = _style_columns(columns) + return cls(columns=columns, styles=styles + extraStyles, **kwargs) def to_json_data(self): - return self.panel_json( - { - "color": { - "mode": self.colorMode - }, - 'columns': self.columns, - 'fontSize': self.fontSize, - 'fieldConfig': { - 'defaults': { - 'custom': { - 'align': self.align, - 'displayMode': self.displayMode, - 'filterable': self.filterable - }, - }, - 'overrides': self.overrides - }, - 'hideTimeOverride': self.hideTimeOverride, - 'mappings': self.mappings, - 'minSpan': self.minSpan, - 'options': { - 'showHeader': self.showHeader - }, - 'type': TABLE_TYPE, - } - ) + return { + 'columns': self.columns, + 'datasource': self.dataSource, + 'description': self.description, + 'editable': self.editable, + 'fontSize': self.fontSize, + 'height': self.height, + 'hideTimeOverride': self.hideTimeOverride, + 'id': self.id, + 'links': self.links, + 'minSpan': self.minSpan, + 'pageSize': self.pageSize, + 'repeat': self.repeat, + 'scroll': self.scroll, + 'showHeader': self.showHeader, + 'span': self.span, + 'sort': self.sort, + 'styles': self.styles, + 'targets': self.targets, + 'timeFrom': self.timeFrom, + 'title': self.title, + 'transform': self.transform, + 'transparent': self.transparent, + 'type': TABLE_TYPE, + } @attr.s diff --git a/grafanalib/tests/test_core.py b/grafanalib/tests/test_core.py index 6e382e51..2b03610b 100644 --- a/grafanalib/tests/test_core.py +++ b/grafanalib/tests/test_core.py @@ -1,7 +1,6 @@ """Tests for core.""" import random - import grafanalib.core as G import pytest @@ -97,20 +96,6 @@ def test_custom_template_dont_override_options(): assert t.to_json_data()['current']['value'] == '1' -def test_table_styled_columns(): - t = G.Table.with_styled_columns( - columns=[ - (G.Column('Foo', 'foo'), G.ColumnStyle()), - (G.Column('Bar', 'bar'), None), - ], - type='custom', - ) - - assert len(t.to_json_data()['options']) == 3 - assert t.to_json_data()['current']['text'] == 'some text 1' - assert t.to_json_data()['current']['value'] == '1' - - def test_table(): t = G.Table( dataSource='some data source', @@ -1205,62 +1190,3 @@ def test_sql_target_with_source_files(): assert t.to_json_data()["targets"][0].rawQuery is True assert t.to_json_data()["targets"][0].rawSql == "SELECT example\nFROM test\nWHERE example='example' AND example_date BETWEEN '1970-01-01' AND '1971-01-01';\n" print(t.to_json_data()["targets"][0]) - - -CW_TESTDATA = [ - pytest.param( - {}, - {'region': '', - 'namespace': '', - 'metricName': '', - 'statistics': [], - 'dimensions': {}, - 'id': '', - 'expression': '', - 'period': '', - 'alias': '', - 'highResolution': False, - 'refId': '', - 'datasource': '', - 'hide': False}, - id='defaults', - ), - pytest.param( - { - 'region': 'us-east-1', - 'namespace': 'AWS/RDS', - 'metricName': 'CPUUtilization', - 'statistics': ['Average'], - 'dimensions': {'DBInstanceIdentifier': 'foo'}, - 'id': 'id', - 'expression': 'expr', - 'period': 'period', - 'alias': 'alias', - 'highResolution': True, - 'refId': 'A', - 'datasource': 'CloudWatch', - 'hide': True, - }, - { - 'region': 'us-east-1', - 'namespace': 'AWS/RDS', - 'metricName': 'CPUUtilization', - 'statistics': ['Average'], - 'dimensions': {'DBInstanceIdentifier': 'foo'}, - 'id': 'id', - 'expression': 'expr', - 'period': 'period', - 'alias': 'alias', - 'highResolution': True, - 'refId': 'A', - 'datasource': 'CloudWatch', - 'hide': True, - }, - id='custom', - ) -] - - -@pytest.mark.parametrize("attrs,expected", CW_TESTDATA) -def test_cloud_watch_target_json_data(attrs, expected): - assert G.CloudWatchTarget(**attrs).to_json_data() == expected diff --git a/grafanalib/tests/test_grafanalib.py b/grafanalib/tests/test_grafanalib.py index 3a53ef57..a8a06cdb 100644 --- a/grafanalib/tests/test_grafanalib.py +++ b/grafanalib/tests/test_grafanalib.py @@ -181,49 +181,10 @@ def test_auto_refids(): assert dashboard.rows[0].panels[0].targets[52].refId == 'BA' -def test_auto_refids(): - """ - auto_ref_ids() provides refIds for all targets without refIds already set. - """ - dashboard = G.Dashboard( - title="Test dashboard", - rows=[ - G.Row(panels=[ - G.Graph( - title="CPU Usage by Namespace (rate[5m])", - dataSource="My data source", - targets=[ - G.Target( - expr='whatever #Q', - legendFormat='{{namespace}}', - ), - G.Target( - expr='hidden whatever', - legendFormat='{{namespace}}', - refId='Q', - hide=True - ), - G.Target( - expr='another target' - ), - ], - yAxes=[ - G.YAxis(format=G.SHORT_FORMAT, label="CPU seconds"), - G.YAxis(format=G.SHORT_FORMAT), - ], - ).auto_ref_ids() - ]), - ], - ) - assert dashboard.rows[0].panels[0].targets[0].refId == 'A' - assert dashboard.rows[0].panels[0].targets[1].refId == 'Q' - assert dashboard.rows[0].panels[0].targets[2].refId == 'B' - - def test_row_show_title(): row = G.Row().to_json_data() - assert row['title'] == 'New row' - assert not row['showTitle'] + assert row['title'] == '' + assert row['showTitle'] row = G.Row(title='My title').to_json_data() assert row['title'] == 'My title'