Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e0706f9bd7 | |||
| 962089bc2b | |||
| ae61bb923f | |||
| d69b54d41a | |||
| ceaa30d88e | |||
| 5e4971ac01 | |||
| 266d8cab89 | |||
| 1bf2ad37f3 | |||
| ca628832ab | |||
| 1cbec50866 | |||
| 743f8420bc | |||
| e1cec1069c | |||
| e9dd84f9d3 | |||
| 660b9a3126 | |||
| b924884240 | |||
| 605de54852 | |||
| 9b674b3944 | |||
| d249335a6c | |||
| 6aa58182c7 | |||
| bcabffc25b | |||
| e58e7cb4c5 | |||
| 74c118f1d1 | |||
| ac9774e7bb | |||
| 31d619c7de | |||
| ecac5d6931 | |||
| 6e2c5eb52a | |||
| 76d08989f0 | |||
| a28c96090c | |||
| 1292d203a8 | |||
| 2ed7ceb59d | |||
| 98908f7b98 | |||
| c8da0ac1c8 | |||
| add6a0d00a | |||
| c74c7e24e2 | |||
| 7ec87ee76b | |||
| 5190949950 | |||
| 6f4625bb78 | |||
| 3680b95b44 | |||
| 04e7970375 | |||
| f2ad3242be | |||
| aa89210c9d | |||
| a5834d3250 | |||
| f5efef1370 | |||
| 3bbc40a32f | |||
| bf7fb67f73 | |||
| ffa9429c68 | |||
| 6649c5d75b | |||
| d6e8129588 | |||
| 6a3a2f5f94 | |||
| 5d3a60d46e | |||
| 5f0a7f43c3 | |||
| ebff883016 | |||
| 81ff856568 | |||
| 648aa62264 |
+28
-2
@@ -7,12 +7,17 @@ aliases:
|
|||||||
only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||||
- &filter-not-release-or-master
|
- &filter-not-release-or-master
|
||||||
tags:
|
tags:
|
||||||
ignore: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
ignore: /^v[0--9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||||
branches:
|
branches:
|
||||||
ignore: master
|
ignore: master
|
||||||
- &filter-only-master
|
- &filter-only-master
|
||||||
branches:
|
branches:
|
||||||
only: master
|
only: master
|
||||||
|
- &filter-only-master-but-not-release
|
||||||
|
tags:
|
||||||
|
ignore: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||||
|
branches:
|
||||||
|
only: master
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
|
|
||||||
@@ -22,7 +27,7 @@ jobs:
|
|||||||
- image: circleci/golang:1.12.6
|
- image: circleci/golang:1.12.6
|
||||||
- image: circleci/mysql:5.6-ram
|
- image: circleci/mysql:5.6-ram
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: rootpass
|
MYSQL_ROOT_PASSWORD: rootpasss
|
||||||
MYSQL_DATABASE: grafana_tests
|
MYSQL_DATABASE: grafana_tests
|
||||||
MYSQL_USER: grafana
|
MYSQL_USER: grafana
|
||||||
MYSQL_PASSWORD: password
|
MYSQL_PASSWORD: password
|
||||||
@@ -623,6 +628,21 @@ jobs:
|
|||||||
echo "-- no changes to docs files --"
|
echo "-- no changes to docs files --"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
release-next-packages:
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:10
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run:
|
||||||
|
name: Boostrap lerna
|
||||||
|
command: 'npx lerna bootstrap'
|
||||||
|
- run:
|
||||||
|
name: npm - Prepare auth token
|
||||||
|
command: 'echo //registry.npmjs.org/:_authToken=$NPM_TOKEN >> ~/.npmrc'
|
||||||
|
- run:
|
||||||
|
name: Release next packages
|
||||||
|
command: './scripts/circle-release-next-packages.sh'
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
version: 2
|
version: 2
|
||||||
build-master:
|
build-master:
|
||||||
@@ -694,6 +714,11 @@ workflows:
|
|||||||
requires:
|
requires:
|
||||||
- end-to-end-test
|
- end-to-end-test
|
||||||
filters: *filter-only-master
|
filters: *filter-only-master
|
||||||
|
- release-next-packages:
|
||||||
|
requires:
|
||||||
|
- test-frontend
|
||||||
|
- build-fast-frontend
|
||||||
|
filters: *filter-only-master-but-not-release
|
||||||
release:
|
release:
|
||||||
jobs:
|
jobs:
|
||||||
- build-all:
|
- build-all:
|
||||||
@@ -803,3 +828,4 @@ workflows:
|
|||||||
- postgres-integration-test
|
- postgres-integration-test
|
||||||
- cache-server-test
|
- cache-server-test
|
||||||
filters: *filter-not-release-or-master
|
filters: *filter-not-release-or-master
|
||||||
|
|
||||||
|
|||||||
+70
-1
@@ -1,4 +1,73 @@
|
|||||||
# 6.3.0 (unreleased)
|
# 6.4.0 (unreleased)
|
||||||
|
|
||||||
|
# 6.3.0-beta1
|
||||||
|
### Features / Enhancements
|
||||||
|
* **Alerting**: Add tags to alert rules. [#10989](https://github.com/grafana/grafana/pull/10989), [@Thib17](https://github.com/Thib17)
|
||||||
|
* **Alerting**: Attempt to send email notifications to all given email addresses. [#16881](https://github.com/grafana/grafana/pull/16881), [@zhulongcheng](https://github.com/zhulongcheng)
|
||||||
|
* **Alerting**: Improve alert rule testing. [#16286](https://github.com/grafana/grafana/pull/16286), [@marefr](https://github.com/marefr)
|
||||||
|
* **Alerting**: Support for configuring content field for Discord alert notifier. [#17017](https://github.com/grafana/grafana/pull/17017), [@jan25](https://github.com/jan25)
|
||||||
|
* **Alertmanager**: Replace illegal chars with underscore in label names. [#17002](https://github.com/grafana/grafana/pull/17002), [@bergquist](https://github.com/bergquist)
|
||||||
|
* **Auth**: Allow expiration of API keys. [#17678](https://github.com/grafana/grafana/pull/17678), [@papagian](https://github.com/papagian)
|
||||||
|
* **Auth**: Return device, os and browser when listing user auth tokens in HTTP API. [#17504](https://github.com/grafana/grafana/pull/17504), [@shavonn](https://github.com/shavonn)
|
||||||
|
* **Auth**: Support list and revoke of user auth tokens in UI. [#17434](https://github.com/grafana/grafana/pull/17434), [@shavonn](https://github.com/shavonn)
|
||||||
|
* **AzureMonitor**: change clashing built-in Grafana variables/macro names for Azure Logs. [#17140](https://github.com/grafana/grafana/pull/17140), [@shavonn](https://github.com/shavonn)
|
||||||
|
* **CloudWatch**: Made region visible for AWS Cloudwatch Expressions. [#17243](https://github.com/grafana/grafana/pull/17243), [@utkarshcmu](https://github.com/utkarshcmu)
|
||||||
|
* **Cloudwatch**: Add AWS DocDB metrics. [#17241](https://github.com/grafana/grafana/pull/17241), [@utkarshcmu](https://github.com/utkarshcmu)
|
||||||
|
* **Dashboard**: Use timezone dashboard setting when exporting to CSV. [#18002](https://github.com/grafana/grafana/pull/18002), [@dehrax](https://github.com/dehrax)
|
||||||
|
* **Data links**. [#17267](https://github.com/grafana/grafana/pull/17267), [@torkelo](https://github.com/torkelo)
|
||||||
|
* **Docker**: Switch base image to ubuntu:latest from debian:stretch to avoid security issues.. [#17066](https://github.com/grafana/grafana/pull/17066), [@bergquist](https://github.com/bergquist)
|
||||||
|
* **Elasticsearch**: Support for visualizing logs in Explore . [#17605](https://github.com/grafana/grafana/pull/17605), [@marefr](https://github.com/marefr)
|
||||||
|
* **Explore**: Adds Live option for supported datasources. [#17062](https://github.com/grafana/grafana/pull/17062), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||||
|
* **Explore**: Adds orgId to URL for sharing purposes. [#17895](https://github.com/grafana/grafana/pull/17895), [@kaydelaney](https://github.com/kaydelaney)
|
||||||
|
* **Explore**: Adds support for new loki 'start' and 'end' params for labels endpoint. [#17512](https://github.com/grafana/grafana/pull/17512), [@kaydelaney](https://github.com/kaydelaney)
|
||||||
|
* **Explore**: Adds support for toggling raw query mode in explore. [#17870](https://github.com/grafana/grafana/pull/17870), [@kaydelaney](https://github.com/kaydelaney)
|
||||||
|
* **Explore**: Allow switching between metrics and logs . [#16959](https://github.com/grafana/grafana/pull/16959), [@marefr](https://github.com/marefr)
|
||||||
|
* **Explore**: Combines the timestamp and local time columns into one. [#17775](https://github.com/grafana/grafana/pull/17775), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||||
|
* **Explore**: Display log lines context . [#17097](https://github.com/grafana/grafana/pull/17097), [@dprokop](https://github.com/dprokop)
|
||||||
|
* **Explore**: Don't parse log levels if provided by field or label. [#17180](https://github.com/grafana/grafana/pull/17180), [@marefr](https://github.com/marefr)
|
||||||
|
* **Explore**: Improves performance of Logs element by limiting re-rendering. [#17685](https://github.com/grafana/grafana/pull/17685), [@kaydelaney](https://github.com/kaydelaney)
|
||||||
|
* **Explore**: Support for new LogQL filtering syntax. [#16674](https://github.com/grafana/grafana/pull/16674), [@davkal](https://github.com/davkal)
|
||||||
|
* **Explore**: Use new TimePicker from Grafana/UI. [#17793](https://github.com/grafana/grafana/pull/17793), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||||
|
* **Explore**: handle newlines in LogRow Highlighter. [#17425](https://github.com/grafana/grafana/pull/17425), [@rrfeng](https://github.com/rrfeng)
|
||||||
|
* **Graph**: Added new fill gradient option. [#17528](https://github.com/grafana/grafana/pull/17528), [@torkelo](https://github.com/torkelo)
|
||||||
|
* **GraphPanel**: Don't sort series when legend table & sort column is not visible . [#17095](https://github.com/grafana/grafana/pull/17095), [@shavonn](https://github.com/shavonn)
|
||||||
|
* **InfluxDB**: Support for visualizing logs in Explore. [#17450](https://github.com/grafana/grafana/pull/17450), [@hugohaggmark](https://github.com/hugohaggmark)
|
||||||
|
* **Logging**: Login and Logout actions (#17760). [#17883](https://github.com/grafana/grafana/pull/17883), [@ATTron](https://github.com/ATTron)
|
||||||
|
* **Logging**: Move log package to pkg/infra. [#17023](https://github.com/grafana/grafana/pull/17023), [@zhulongcheng](https://github.com/zhulongcheng)
|
||||||
|
* **Metrics**: Expose stats about roles as metrics. [#17469](https://github.com/grafana/grafana/pull/17469), [@bergquist](https://github.com/bergquist)
|
||||||
|
* **MySQL/Postgres/MSSQL**: Add parsing for day, weeks and year intervals in macros. [#13086](https://github.com/grafana/grafana/pull/13086), [@bernardd](https://github.com/bernardd)
|
||||||
|
* **MySQL**: Add support for periodically reloading client certs. [#14892](https://github.com/grafana/grafana/pull/14892), [@tpetr](https://github.com/tpetr)
|
||||||
|
* **Plugins**: replace dataFormats list with skipDataQuery flag in plugin.json. [#16984](https://github.com/grafana/grafana/pull/16984), [@ryantxu](https://github.com/ryantxu)
|
||||||
|
* **Prometheus**: Take timezone into account for step alignment. [#17477](https://github.com/grafana/grafana/pull/17477), [@fxmiii](https://github.com/fxmiii)
|
||||||
|
* **Prometheus**: Use overridden panel range for $__range instead of dashboard range. [#17352](https://github.com/grafana/grafana/pull/17352), [@patrick246](https://github.com/patrick246)
|
||||||
|
* **Prometheus**: added time range filter to series labels query. [#16851](https://github.com/grafana/grafana/pull/16851), [@FUSAKLA](https://github.com/FUSAKLA)
|
||||||
|
* **Provisioning**: Support folder that doesn't exist yet in dashboard provisioning. [#17407](https://github.com/grafana/grafana/pull/17407), [@Nexucis](https://github.com/Nexucis)
|
||||||
|
* **Refresh picker**: Handle empty intervals. [#17585](https://github.com/grafana/grafana/pull/17585), [@dehrax](https://github.com/dehrax)
|
||||||
|
* **Singlestat**: Add y min/max config to singlestat sparklines. [#17527](https://github.com/grafana/grafana/pull/17527), [@pitr](https://github.com/pitr)
|
||||||
|
* **Snapshot**: use given key and deleteKey. [#16876](https://github.com/grafana/grafana/pull/16876), [@zhulongcheng](https://github.com/zhulongcheng)
|
||||||
|
* **Templating**: Correctly display __text in multi-value variable after page reload. [#17840](https://github.com/grafana/grafana/pull/17840), [@EduardSergeev](https://github.com/EduardSergeev)
|
||||||
|
* **Templating**: Support selecting all filtered values of a multi-value variable. [#16873](https://github.com/grafana/grafana/pull/16873), [@r66ad](https://github.com/r66ad)
|
||||||
|
* **Tracing**: allow propagation with Zipkin headers. [#17009](https://github.com/grafana/grafana/pull/17009), [@jrockway](https://github.com/jrockway)
|
||||||
|
* **Users**: Disable users removed from LDAP. [#16820](https://github.com/grafana/grafana/pull/16820), [@alexanderzobnin](https://github.com/alexanderzobnin)
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
* **AddPanel**: Fix issue when removing moved add panel widget . [#17659](https://github.com/grafana/grafana/pull/17659), [@dehrax](https://github.com/dehrax)
|
||||||
|
* **CLI**: Fix encrypt-datasource-passwords fails with sql error. [#18014](https://github.com/grafana/grafana/pull/18014), [@marefr](https://github.com/marefr)
|
||||||
|
* **Elasticsearch**: Fix default max concurrent shard requests. [#17770](https://github.com/grafana/grafana/pull/17770), [@marefr](https://github.com/marefr)
|
||||||
|
* **Explore**: Fix browsing back to dashboard panel. [#17061](https://github.com/grafana/grafana/pull/17061), [@jschill](https://github.com/jschill)
|
||||||
|
* **Explore**: Fix filter by series level in logs graph. [#17798](https://github.com/grafana/grafana/pull/17798), [@marefr](https://github.com/marefr)
|
||||||
|
* **Explore**: Fix issues when loading and both graph/table are collapsed. [#17113](https://github.com/grafana/grafana/pull/17113), [@marefr](https://github.com/marefr)
|
||||||
|
* **Explore**: Fix selection/copy of log lines. [#17121](https://github.com/grafana/grafana/pull/17121), [@marefr](https://github.com/marefr)
|
||||||
|
* **Fix**: Wrap value of multi variable in array when coming from URL. [#16992](https://github.com/grafana/grafana/pull/16992), [@aocenas](https://github.com/aocenas)
|
||||||
|
* **Frontend**: Fix for Json tree component not working. [#17608](https://github.com/grafana/grafana/pull/17608), [@srid12](https://github.com/srid12)
|
||||||
|
* **Graphite**: Fix for issue with alias function being moved last. [#17791](https://github.com/grafana/grafana/pull/17791), [@torkelo](https://github.com/torkelo)
|
||||||
|
* **Graphite**: Fixes issue with seriesByTag & function with variable param. [#17795](https://github.com/grafana/grafana/pull/17795), [@torkelo](https://github.com/torkelo)
|
||||||
|
* **Graphite**: use POST for /metrics/find requests. [#17814](https://github.com/grafana/grafana/pull/17814), [@papagian](https://github.com/papagian)
|
||||||
|
* **HTTP Server**: Serve Grafana with a custom URL path prefix. [#17048](https://github.com/grafana/grafana/pull/17048), [@jan25](https://github.com/jan25)
|
||||||
|
* **InfluxDB**: Fixes single quotes are not escaped in label value filters. [#17398](https://github.com/grafana/grafana/pull/17398), [@Panzki](https://github.com/Panzki)
|
||||||
|
* **Prometheus**: Correctly escape '|' literals in interpolated PromQL variables. [#16932](https://github.com/grafana/grafana/pull/16932), [@Limess](https://github.com/Limess)
|
||||||
|
* **Prometheus**: Fix when adding label for metrics which contains colons in Explore. [#16760](https://github.com/grafana/grafana/pull/16760), [@tolwi](https://github.com/tolwi)
|
||||||
|
* **SinglestatPanel**: Remove background color when value turns null. [#17552](https://github.com/grafana/grafana/pull/17552), [@druggieri](https://github.com/druggieri)
|
||||||
|
|
||||||
# 6.2.5 (2019-06-25)
|
# 6.2.5 (2019-06-25)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ GO_FILES := ./pkg/...
|
|||||||
all: deps build
|
all: deps build
|
||||||
|
|
||||||
deps-go:
|
deps-go:
|
||||||
go run build.go setup
|
$(GO) run build.go setup
|
||||||
|
|
||||||
deps-js: node_modules
|
deps-js: node_modules
|
||||||
|
|
||||||
@@ -16,15 +16,15 @@ deps: deps-js
|
|||||||
|
|
||||||
build-go:
|
build-go:
|
||||||
@echo "build go files"
|
@echo "build go files"
|
||||||
GO111MODULE=on go run build.go build
|
$(GO) run build.go build
|
||||||
|
|
||||||
build-server:
|
build-server:
|
||||||
@echo "build server"
|
@echo "build server"
|
||||||
GO111MODULE=on go run build.go build-server
|
$(GO) run build.go build-server
|
||||||
|
|
||||||
build-cli:
|
build-cli:
|
||||||
@echo "build in CI environment"
|
@echo "build in CI environment"
|
||||||
GO111MODULE=on go run build.go build-cli
|
$(GO) run build.go build-cli
|
||||||
|
|
||||||
build-js:
|
build-js:
|
||||||
@echo "build frontend"
|
@echo "build frontend"
|
||||||
@@ -35,7 +35,7 @@ build: build-go build-js
|
|||||||
build-docker-dev:
|
build-docker-dev:
|
||||||
@echo "build development container"
|
@echo "build development container"
|
||||||
@echo "\033[92mInfo:\033[0m the frontend code is expected to be built already."
|
@echo "\033[92mInfo:\033[0m the frontend code is expected to be built already."
|
||||||
GO111MODULE=on go run build.go -goos linux -pkg-arch amd64 ${OPT} build pkg-archive latest
|
$(GO) run build.go -goos linux -pkg-arch amd64 ${OPT} build pkg-archive latest
|
||||||
cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
|
cp dist/grafana-latest.linux-x64.tar.gz packaging/docker
|
||||||
cd packaging/docker && docker build --tag grafana/grafana:dev .
|
cd packaging/docker && docker build --tag grafana/grafana:dev .
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ build-docker-full:
|
|||||||
|
|
||||||
test-go:
|
test-go:
|
||||||
@echo "test backend"
|
@echo "test backend"
|
||||||
GO111MODULE=on go test -v ./pkg/...
|
$(GO) test -v ./pkg/...
|
||||||
|
|
||||||
test-js:
|
test-js:
|
||||||
@echo "test frontend"
|
@echo "test frontend"
|
||||||
@@ -107,7 +107,7 @@ golangci-lint: scripts/go/bin/golangci-lint
|
|||||||
|
|
||||||
go-vet:
|
go-vet:
|
||||||
@echo "lint via go vet"
|
@echo "lint via go vet"
|
||||||
@go vet $(GO_FILES)
|
@$(GO) vet $(GO_FILES)
|
||||||
|
|
||||||
lint-go: go-vet golangci-lint revive revive-alerting gosec
|
lint-go: go-vet golangci-lint revive revive-alerting gosec
|
||||||
|
|
||||||
|
|||||||
@@ -147,12 +147,34 @@ Writing & watching frontend tests
|
|||||||
```bash
|
```bash
|
||||||
# Run Golang tests using sqlite3 as database (default)
|
# Run Golang tests using sqlite3 as database (default)
|
||||||
go test ./pkg/...
|
go test ./pkg/...
|
||||||
|
```
|
||||||
|
|
||||||
# Run Golang tests using mysql as database - convenient to use /docker/blocks/mysql_tests
|
##### Running the MySQL or Postgres backend tests:
|
||||||
GRAFANA_TEST_DB=mysql go test ./pkg/...
|
|
||||||
|
|
||||||
# Run Golang tests using postgres as database - convenient to use /docker/blocks/postgres_tests
|
Run these by setting `GRAFANA_TEST_DB` in your environment.
|
||||||
GRAFANA_TEST_DB=postgres go test ./pkg/...
|
|
||||||
|
- `GRAFANA_TEST_DB=mysql` to test MySQL
|
||||||
|
- `GRAFANA_TEST_DB=postgres` to test Postgres
|
||||||
|
|
||||||
|
Follow the instructions in `./devenv` to spin up test containers running the appropriate databases with `docker-compose`
|
||||||
|
- Use `docker/blocks/mysql_tests` or `docker/blocks/postgres_tests` as appropriate
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# MySQL
|
||||||
|
# Tests can only be ran in one Go package at a time due to clashing db queries. To run MySQL tests for the "pkg/services/sqlstore" package, run:
|
||||||
|
GRAFANA_TEST_DB=mysql go test ./pkg/services/sqlstore/...
|
||||||
|
|
||||||
|
# Or run all the packages using the circle CI scripts. This method will be slower as the scripts will run all the tests, including the integration tests.
|
||||||
|
./scripts/circle-test-mysql.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Postgres
|
||||||
|
# Tests can only be ran in one Go package at a time due to clashing db queries. To run Postgres tests for the "pkg/services/sqlstore" package, run:
|
||||||
|
GRAFANA_TEST_DB=postgres go test ./pkg/services/sqlstore/...
|
||||||
|
|
||||||
|
# Or run all the packages using the circle CI scripts. This method will be slower as the scripts will run all the tests, including the integration tests.
|
||||||
|
./scripts/circle-test-postgres.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
#### End-to-end
|
#### End-to-end
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# root_url = %(protocol)s://%(domain)s:10080/grafana/
|
# root_url = %(protocol)s://%(domain)s:10080/grafana/
|
||||||
|
|
||||||
nginxproxy:
|
nginxproxy:
|
||||||
build: docker/blocks/nginx_proxy
|
build: docker/blocks/nginx_proxy_mac
|
||||||
ports:
|
ports:
|
||||||
- "10080:10080"
|
- "10080:10080"
|
||||||
|
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ aliases = ["/v1.1", "/guides/reference/admin", "/v3.1"]
|
|||||||
<h4>Provisioning</h4>
|
<h4>Provisioning</h4>
|
||||||
<p>A guide to help you automate your Grafana setup & configuration.</p>
|
<p>A guide to help you automate your Grafana setup & configuration.</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{< relref "guides/whats-new-in-v6-2.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
<a href="{{< relref "guides/whats-new-in-v6-3.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||||
<h4>What's new in v6.2</h4>
|
<h4>What's new in v6.3</h4>
|
||||||
<p>Article on all the new cool features and enhancements in v6.2</p>
|
<p>Article on all the new cool features and enhancements in v6.3</p>
|
||||||
</a>
|
</a>
|
||||||
<a href="{{< relref "tutorials/screencasts.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
<a href="{{< relref "tutorials/screencasts.md" >}}" class="nav-cards__item nav-cards__item--guide">
|
||||||
<h4>Screencasts</h4>
|
<h4>Screencasts</h4>
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ datasources:
|
|||||||
password: $PASSWORD
|
password: $PASSWORD
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If you have a literal `$` in your value and want to avoid interpolation, `$$` can be used.
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
## Configuration Management Tools
|
## Configuration Management Tools
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ header_name = X-WEBAUTH-USER
|
|||||||
header_property = username
|
header_property = username
|
||||||
# Set to `true` to enable auto sign up of users who do not exist in Grafana DB. Defaults to `true`.
|
# Set to `true` to enable auto sign up of users who do not exist in Grafana DB. Defaults to `true`.
|
||||||
auto_sign_up = true
|
auto_sign_up = true
|
||||||
# If combined with Grafana LDAP integration define sync interval
|
# If combined with Grafana LDAP integration define sync interval in minutes
|
||||||
ldap_sync_ttl = 60
|
ldap_sync_ttl = 60
|
||||||
# Limit where auth proxy requests come from by configuring a list of IP addresses.
|
# Limit where auth proxy requests come from by configuring a list of IP addresses.
|
||||||
# This can be used to prevent users spoofing the X-WEBAUTH-USER header.
|
# This can be used to prevent users spoofing the X-WEBAUTH-USER header.
|
||||||
|
|||||||
@@ -99,3 +99,18 @@ allow_sign_up = true
|
|||||||
allowed_organizations = github google
|
allowed_organizations = github google
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Team Sync (Enterprise only)
|
||||||
|
|
||||||
|
> Only available in Grafana Enterprise v6.3+
|
||||||
|
|
||||||
|
With Team Sync you can map your GitHub org teams to teams in Grafana so that your users will automatically be added to
|
||||||
|
the correct teams.
|
||||||
|
|
||||||
|
Your GitHub teams can be referenced in two ways:
|
||||||
|
|
||||||
|
- `https://github.com/orgs/<org>/teams/<team name>`
|
||||||
|
- `@<org>/<team name>`
|
||||||
|
|
||||||
|
Example: `@grafana/developers`
|
||||||
|
|
||||||
|
[Learn more about Team Sync]({{< relref "auth/enhanced_ldap.md" >}})
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
+++
|
||||||
|
title = "What's New in Grafana v6.3"
|
||||||
|
description = "Feature & improvement highlights for Grafana v6.3"
|
||||||
|
keywords = ["grafana", "new", "documentation", "6.3"]
|
||||||
|
type = "docs"
|
||||||
|
[menu.docs]
|
||||||
|
name = "Version 6.3"
|
||||||
|
identifier = "v6.3"
|
||||||
|
parent = "whatsnew"
|
||||||
|
weight = -14
|
||||||
|
+++
|
||||||
|
|
||||||
|
# What's New in Grafana v6.3
|
||||||
|
|
||||||
|
For all details please read the full [CHANGELOG.md](https://github.com/grafana/grafana/blob/master/CHANGELOG.md)
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
|
||||||
|
- New Explore features
|
||||||
|
- [Loki Live Streaming]({{< relref "#loki-live-streaming" >}})
|
||||||
|
- [Loki Context Queries]({{< relref "#loki-context-queries" >}})
|
||||||
|
- [Elasticsearch Logs Support]({{< relref "#elasticsearch-logs-support" >}})
|
||||||
|
- [InfluxDB Logs Support]({{< relref "#influxdb-logs-support" >}})
|
||||||
|
- [Data links]({{< relref "#data-links" >}})
|
||||||
|
- [New Time Picker]({{< relref "#new-time-picker" >}})
|
||||||
|
- [Graph Area Gradients]({{< relref "#graph-gradients" >}}) - A new graph display option!
|
||||||
|
- Grafana Enterprise
|
||||||
|
- [LDAP Active Sync]({{< relref "#ldap-active-sync" >}}) - LDAP Active Sync
|
||||||
|
- [SAML Authentication]({{< relref "#saml-authentication" >}}) - SAML Authentication
|
||||||
|
|
||||||
|
## Explore improvements
|
||||||
|
|
||||||
|
This release adds a ton of enhancements to Explore. Both in terms of new general enhancements but also in
|
||||||
|
new data source specific features.
|
||||||
|
|
||||||
|
### Loki live streaming
|
||||||
|
|
||||||
|
For log queries using the Loki data source you can now stream logs live directly to the Explore UI.
|
||||||
|
|
||||||
|
### Loki context queries
|
||||||
|
|
||||||
|
After finding a log line through the heavy use of query filters it can then be useful to
|
||||||
|
see the log lines surrounding the line your searched for. The `show context` feature
|
||||||
|
allows you to view lines before and after the line of interest.
|
||||||
|
|
||||||
|
### Elasticsearch logs support
|
||||||
|
|
||||||
|
This release adds support for searching & visualizing logs stored in Elasticsearch in the Explore mode. With a special
|
||||||
|
simplified query interface specifically designed for logs search.
|
||||||
|
|
||||||
|
{{< docs-imagebox img="/img/docs/v63/elasticsearch_explore_logs.png" max-width="600px" caption="New Time Picker" >}}
|
||||||
|
|
||||||
|
Please read [Using Elasticsearch in Grafana](/features/datasources/elasticsearch/#querying-logs-beta) for more detailed information on how to get started and use it.
|
||||||
|
|
||||||
|
### InfluxDB logs support
|
||||||
|
|
||||||
|
This release adds support for searching & visualizing logs stored in InfluxDB in the Explore mode. With a special
|
||||||
|
simplified query interface specifically designed for logs search.
|
||||||
|
|
||||||
|
{{< docs-imagebox img="/img/docs/v63/influxdb_explore_logs.png" max-width="600px" caption="New Time Picker" >}}
|
||||||
|
|
||||||
|
Please read [Using InfluxDB in Grafana](/features/datasources/influxdb/#querying-logs-beta) for more detailed information on how to get started and use it.
|
||||||
|
|
||||||
|
## Data Links
|
||||||
|
|
||||||
|
We have simplified the UI for defining panel drilldown links (and renamed them to Panel links). We have also added a
|
||||||
|
new type of link named `Data link`. The reason to have two different types is to make it clear how they are used
|
||||||
|
and what variables you can use in the link. Panel links are only shown in the top left corner of
|
||||||
|
the panel and you cannot reference series name or any data field.
|
||||||
|
|
||||||
|
While `Data links` are used by the actual visualization and can reference data fields.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```url
|
||||||
|
http://my-grafana.com/d/bPCI6VSZz/other-dashboard?var-server=${__series_name}
|
||||||
|
```
|
||||||
|
|
||||||
|
You have access to these variables:
|
||||||
|
|
||||||
|
Name | Description
|
||||||
|
------------ | -------------
|
||||||
|
*${__series_name}* | The name of the time series (or table)
|
||||||
|
*${__value_time}* | The time of the point your clicking on (in millisecond epoch)
|
||||||
|
*${__url_time_range}* | Interpolates as the full time range (i.e. from=21312323412&to=21312312312)
|
||||||
|
*${__all_variables}* | Adds all current variables (and current values) to the url
|
||||||
|
|
||||||
|
You can then click on point in the Graph.
|
||||||
|
|
||||||
|
{{< docs-imagebox img="/img/docs/v63/graph_datalink.png" max-width="400px" caption="New Time Picker" >}}
|
||||||
|
|
||||||
|
For now only the Graph panel supports `Data links` but we hope to add these to many visualizations.
|
||||||
|
|
||||||
|
## New Time Picker
|
||||||
|
|
||||||
|
The time picker has been re-designed and with a more basic design that makes accessing quick ranges more easy.
|
||||||
|
|
||||||
|
{{< docs-imagebox img="/img/docs/v63/time_picker.png" max-width="400px" caption="New Time Picker" >}}
|
||||||
|
|
||||||
|
## Graph Gradients
|
||||||
|
|
||||||
|
Want more eye candy in your graphs? Then the fill gradient option might be for you! Works really well for
|
||||||
|
graphs with only a single series.
|
||||||
|
|
||||||
|
{{< docs-imagebox img="/img/docs/v63/graph_gradient_area.jpeg" max-width="800px" caption="Graph Gradient Area" >}}
|
||||||
|
|
||||||
|
Looks really nice in light theme as well.
|
||||||
|
|
||||||
|
{{< docs-imagebox img="/img/docs/v63/graph_gradients_white.png" max-width="800px" caption="Graph Gradient Area" >}}
|
||||||
|
|
||||||
|
## Grafana Enterprise
|
||||||
|
|
||||||
|
Substantial refactoring and improvements to the external auth systems has gone in to this release making the features
|
||||||
|
listed below possible as well as laying a foundation for future enhancements.
|
||||||
|
|
||||||
|
### LDAP Active Sync
|
||||||
|
|
||||||
|
This is a new Enterprise feature that enables background syncing of user information, org role and teams memberships.
|
||||||
|
This syncing is otherwise only done at login time. With this feature you can schedule how often this user synchronization should
|
||||||
|
occur.
|
||||||
|
|
||||||
|
For example, lets say a user is removed from an LDAP group. In previous versions of Grafana an admin would have to
|
||||||
|
wait for the user to logout or the session to expire for the Grafana permissions to update, a process that can take days.
|
||||||
|
|
||||||
|
With active sync the user would be automatically removed from the corresponding team in Grafana or even logged out and disabled if no longer
|
||||||
|
belonging to an LDAP group that gives them access to Grafana.
|
||||||
|
|
||||||
|
[Read more](/auth/enhanced_ldap/#active-ldap-synchronization)
|
||||||
|
|
||||||
|
### SAML Authentication
|
||||||
|
|
||||||
|
Built-in support for SAML is now available in Grafana Enterprise.
|
||||||
|
|
||||||
|
### Team Sync for GitHub OAuth
|
||||||
|
|
||||||
|
When setting up OAuth with GitHub it's now possible to sync GitHub teams with Teams in Grafana.
|
||||||
|
|
||||||
|
[See docs]({{< relref "auth/github.md" >}})
|
||||||
|
|
||||||
|
### Team Sync for Auth Proxy
|
||||||
|
|
||||||
|
We've added support for enriching the Auth Proxy headers with Teams information, which makes it possible
|
||||||
|
to use Team Sync with Auth Proxy.
|
||||||
|
|
||||||
|
[See docs](/auth/auth-proxy/#auth-proxy-authentication)
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"stable": "6.2.5",
|
"stable": "6.2.5",
|
||||||
"testing": "6.2.5"
|
"testing": "6.3.0-beta1"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,5 +2,5 @@
|
|||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"packages": ["packages/*"],
|
"packages": ["packages/*"],
|
||||||
"version": "6.3.0-alpha.36"
|
"version": "6.4.0-alpha.12"
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-4
@@ -5,7 +5,7 @@
|
|||||||
"company": "Grafana Labs"
|
"company": "Grafana Labs"
|
||||||
},
|
},
|
||||||
"name": "grafana",
|
"name": "grafana",
|
||||||
"version": "6.3.0-pre",
|
"version": "6.4.0-pre",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "http://github.com/grafana/grafana.git"
|
"url": "http://github.com/grafana/grafana.git"
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
"ng-annotate-loader": "0.6.1",
|
"ng-annotate-loader": "0.6.1",
|
||||||
"ng-annotate-webpack-plugin": "0.3.0",
|
"ng-annotate-webpack-plugin": "0.3.0",
|
||||||
"ngtemplate-loader": "2.0.1",
|
"ngtemplate-loader": "2.0.1",
|
||||||
"node-sass": "4.11.0",
|
"node-sass": "4.12.0",
|
||||||
"npm": "6.9.0",
|
"npm": "6.9.0",
|
||||||
"optimize-css-assets-webpack-plugin": "5.0.1",
|
"optimize-css-assets-webpack-plugin": "5.0.1",
|
||||||
"phantomjs-prebuilt": "2.1.16",
|
"phantomjs-prebuilt": "2.1.16",
|
||||||
@@ -148,7 +148,8 @@
|
|||||||
"themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts",
|
"themes:generate": "ts-node --project ./scripts/cli/tsconfig.json ./scripts/cli/generateSassVariableFiles.ts",
|
||||||
"packages:prepare": "lerna run clean && npm run test && lerna version --tag-version-prefix=\"packages@\" -m \"Packages: publish %s\" --no-push",
|
"packages:prepare": "lerna run clean && npm run test && lerna version --tag-version-prefix=\"packages@\" -m \"Packages: publish %s\" --no-push",
|
||||||
"packages:build": "lerna run clean && lerna run build",
|
"packages:build": "lerna run clean && lerna run build",
|
||||||
"packages:publish": "lerna publish from-package --contents dist --tag-version-prefix=\"packages@\" --dist-tag next"
|
"packages:publish": "lerna publish from-package --contents dist",
|
||||||
|
"packages:publishNext": "lerna publish from-package --contents dist --dist-tag next --yes"
|
||||||
},
|
},
|
||||||
"husky": {
|
"husky": {
|
||||||
"hooks": {
|
"hooks": {
|
||||||
@@ -201,7 +202,7 @@
|
|||||||
"file-saver": "1.3.8",
|
"file-saver": "1.3.8",
|
||||||
"immutable": "3.8.2",
|
"immutable": "3.8.2",
|
||||||
"jquery": "3.4.1",
|
"jquery": "3.4.1",
|
||||||
"lodash": "4.17.11",
|
"lodash": "4.17.14",
|
||||||
"marked": "0.6.2",
|
"marked": "0.6.2",
|
||||||
"moment": "2.24.0",
|
"moment": "2.24.0",
|
||||||
"mousetrap": "1.6.3",
|
"mousetrap": "1.6.3",
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# Grafana Data Library
|
# Grafana Data Library
|
||||||
|
|
||||||
The core data components
|
This package holds the root data types and functions used within Grafana.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@grafana/data",
|
"name": "@grafana/data",
|
||||||
"version": "6.3.0-alpha.36",
|
"version": "6.4.0-alpha.12",
|
||||||
"description": "Grafana Data Library",
|
"description": "Grafana Data Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"typescript"
|
"typescript"
|
||||||
@@ -11,8 +11,7 @@
|
|||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"clean": "rimraf ./dist ./compiled",
|
"clean": "rimraf ./dist ./compiled",
|
||||||
"bundle": "rollup -c rollup.config.ts",
|
"bundle": "rollup -c rollup.config.ts",
|
||||||
"build": "grafana-toolkit package:build --scope=data",
|
"build": "grafana-toolkit package:build --scope=data"
|
||||||
"postpublish": "npm run clean"
|
|
||||||
},
|
},
|
||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@@ -37,8 +36,5 @@
|
|||||||
"rollup-plugin-visualizer": "0.9.2",
|
"rollup-plugin-visualizer": "0.9.2",
|
||||||
"sinon": "1.17.6",
|
"sinon": "1.17.6",
|
||||||
"typescript": "3.4.1"
|
"typescript": "3.4.1"
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"@types/lodash": "4.14.119"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx", "../../public/app/types/jquery/*.ts"],
|
"include": ["src/**/*.ts", "src/**/*.tsx", "../../public/app/types/jquery/*.ts"],
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDirs": ["."],
|
"rootDirs": ["."],
|
||||||
"module": "esnext",
|
|
||||||
"outDir": "compiled",
|
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "dist",
|
|
||||||
"strict": true,
|
|
||||||
"alwaysStrict": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"typeRoots": ["./node_modules/@types", "types"],
|
"typeRoots": ["./node_modules/@types", "types"],
|
||||||
"skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors,
|
"declarationDir": "dist",
|
||||||
"removeComments": false
|
"outDir": "compiled"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
# Grafana Runtime library
|
# Grafana Runtime library
|
||||||
|
|
||||||
Interfaces that let you use the runtime...
|
This package allows access to grafana services. It requires Grafana to be running already and the functions to be imported as externals.
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@grafana/runtime",
|
"name": "@grafana/runtime",
|
||||||
"version": "6.3.0-alpha.36",
|
"version": "6.4.0-alpha.12",
|
||||||
"description": "Grafana Runtime Library",
|
"description": "Grafana Runtime Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"typescript",
|
"grafana"
|
||||||
"react",
|
|
||||||
"react-component"
|
|
||||||
],
|
],
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -13,8 +11,7 @@
|
|||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"clean": "rimraf ./dist ./compiled",
|
"clean": "rimraf ./dist ./compiled",
|
||||||
"bundle": "rollup -c rollup.config.ts",
|
"bundle": "rollup -c rollup.config.ts",
|
||||||
"build": "grafana-toolkit package:build --scope=runtime",
|
"build": "grafana-toolkit package:build --scope=runtime"
|
||||||
"postpublish": "npm run clean"
|
|
||||||
},
|
},
|
||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@@ -35,8 +32,5 @@
|
|||||||
"rollup-plugin-typescript2": "0.19.3",
|
"rollup-plugin-typescript2": "0.19.3",
|
||||||
"rollup-plugin-visualizer": "0.9.2",
|
"rollup-plugin-visualizer": "0.9.2",
|
||||||
"typescript": "3.4.1"
|
"typescript": "3.4.1"
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"@types/lodash": "4.14.119"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx", "../../public/app/types/jquery/*.ts"],
|
"include": ["src/**/*.ts", "src/**/*.tsx", "../../public/app/types/jquery/*.ts"],
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDirs": ["."],
|
"rootDirs": ["."],
|
||||||
"module": "esnext",
|
|
||||||
"outDir": "compiled",
|
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "dist",
|
|
||||||
"strict": true,
|
|
||||||
"alwaysStrict": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"typeRoots": ["./node_modules/@types", "types"],
|
"typeRoots": ["./node_modules/@types", "types"],
|
||||||
"skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors,
|
"declarationDir": "dist",
|
||||||
"removeComments": false
|
"outDir": "compiled"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ Adidtionaly, you can also provide additional Jest config via package.json file.
|
|||||||
|
|
||||||
|
|
||||||
## Working with CSS & static assets
|
## Working with CSS & static assets
|
||||||
We support pure css, SASS and CSS in JS approach (via Emotion). All static assets referenced in your code (i.e. images) should be placed under `src/static` directory and referenced using relative paths.
|
We support pure css, SASS and CSS in JS approach (via Emotion).
|
||||||
|
|
||||||
1. Single css/sass file
|
1. Single css/sass file
|
||||||
Create your css/sass file and import it in your plugin entry point (typically module.ts):
|
Create your css/sass file and import it in your plugin entry point (typically module.ts):
|
||||||
@@ -101,6 +101,8 @@ If you want to provide different stylesheets for dark/light theme, create `dark.
|
|||||||
|
|
||||||
TODO: add note about loadPluginCss
|
TODO: add note about loadPluginCss
|
||||||
|
|
||||||
|
Note that static files (png, svg, json, html) are all copied to dist directory when the plugin is bundled. Relative paths to those files does not change.
|
||||||
|
|
||||||
3. Emotion
|
3. Emotion
|
||||||
Starting from Grafana 6.2 our suggested way of styling plugins is by using [Emotion](https://emotion.sh). It's a css-in-js library that we use internaly at Grafana. The biggest advantage of using Emotion is that you will get access to Grafana Theme variables.
|
Starting from Grafana 6.2 our suggested way of styling plugins is by using [Emotion](https://emotion.sh). It's a css-in-js library that we use internaly at Grafana. The biggest advantage of using Emotion is that you will get access to Grafana Theme variables.
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@grafana/toolkit",
|
"name": "@grafana/toolkit",
|
||||||
"version": "6.3.0-alpha.36",
|
"version": "6.4.0-alpha.12",
|
||||||
"description": "Grafana Toolkit",
|
"description": "Grafana Toolkit",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"typescript",
|
"grafana",
|
||||||
"react",
|
"cli",
|
||||||
"react-component"
|
"plugins"
|
||||||
],
|
],
|
||||||
"bin": {
|
"bin": {
|
||||||
"grafana-toolkit": "./bin/grafana-toolkit.js"
|
"grafana-toolkit": "./bin/grafana-toolkit.js"
|
||||||
@@ -15,8 +15,7 @@
|
|||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"precommit": "npm run tslint & npm run typecheck",
|
"precommit": "npm run tslint & npm run typecheck",
|
||||||
"clean": "rimraf ./dist ./compiled",
|
"clean": "rimraf ./dist ./compiled",
|
||||||
"build": "grafana-toolkit toolkit:build",
|
"build": "grafana-toolkit toolkit:build"
|
||||||
"postpublish": "npm run clean"
|
|
||||||
},
|
},
|
||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
@@ -30,6 +29,7 @@
|
|||||||
"@types/node": "^12.0.4",
|
"@types/node": "^12.0.4",
|
||||||
"@types/react-dev-utils": "^9.0.1",
|
"@types/react-dev-utils": "^9.0.1",
|
||||||
"@types/semver": "^6.0.0",
|
"@types/semver": "^6.0.0",
|
||||||
|
"@types/tmp": "^0.1.0",
|
||||||
"@types/webpack": "4.4.34",
|
"@types/webpack": "4.4.34",
|
||||||
"axios": "0.19.0",
|
"axios": "0.19.0",
|
||||||
"babel-loader": "8.0.6",
|
"babel-loader": "8.0.6",
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"jest": "24.8.0",
|
"jest": "24.8.0",
|
||||||
"jest-cli": "^24.8.0",
|
"jest-cli": "^24.8.0",
|
||||||
"jest-coverage-badges": "^1.1.2",
|
"jest-coverage-badges": "^1.1.2",
|
||||||
"lodash": "4.17.11",
|
"lodash": "4.17.14",
|
||||||
"mini-css-extract-plugin": "^0.7.0",
|
"mini-css-extract-plugin": "^0.7.0",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.12.0",
|
||||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||||
@@ -74,9 +74,6 @@
|
|||||||
"url-loader": "^2.0.1",
|
"url-loader": "^2.0.1",
|
||||||
"webpack": "4.35.0"
|
"webpack": "4.35.0"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
|
||||||
"@types/lodash": "4.14.119"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/glob": "^7.1.1",
|
"@types/glob": "^7.1.1",
|
||||||
"@types/prettier": "^1.16.4"
|
"@types/prettier": "^1.16.4"
|
||||||
|
|||||||
@@ -13,7 +13,14 @@ import { pluginTestTask } from './tasks/plugin.tests';
|
|||||||
import { searchTestDataSetupTask } from './tasks/searchTestDataSetup';
|
import { searchTestDataSetupTask } from './tasks/searchTestDataSetup';
|
||||||
import { closeMilestoneTask } from './tasks/closeMilestone';
|
import { closeMilestoneTask } from './tasks/closeMilestone';
|
||||||
import { pluginDevTask } from './tasks/plugin.dev';
|
import { pluginDevTask } from './tasks/plugin.dev';
|
||||||
import { pluginCITask } from './tasks/plugin.ci';
|
import {
|
||||||
|
ciBuildPluginTask,
|
||||||
|
ciBuildPluginDocsTask,
|
||||||
|
ciBundlePluginTask,
|
||||||
|
ciTestPluginTask,
|
||||||
|
ciPluginReportTask,
|
||||||
|
ciDeployPluginTask,
|
||||||
|
} from './tasks/plugin.ci';
|
||||||
import { buildPackageTask } from './tasks/package.build';
|
import { buildPackageTask } from './tasks/package.build';
|
||||||
|
|
||||||
export const run = (includeInternalScripts = false) => {
|
export const run = (includeInternalScripts = false) => {
|
||||||
@@ -141,15 +148,53 @@ export const run = (includeInternalScripts = false) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('plugin:ci')
|
.command('plugin:ci-build')
|
||||||
.option('--dryRun', "Dry run (don't post results)")
|
.option('--backend <backend>', 'For backend task, which backend to run')
|
||||||
.description('Run Plugin CI task')
|
.description('Build the plugin, leaving artifacts in /dist')
|
||||||
.action(async cmd => {
|
.action(async cmd => {
|
||||||
await execTask(pluginCITask)({
|
await execTask(ciBuildPluginTask)({
|
||||||
dryRun: cmd.dryRun,
|
backend: cmd.backend,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('plugin:ci-docs')
|
||||||
|
.description('Build the HTML docs')
|
||||||
|
.action(async cmd => {
|
||||||
|
await execTask(ciBuildPluginDocsTask)({});
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('plugin:ci-bundle')
|
||||||
|
.description('Create a zip artifact for the plugin')
|
||||||
|
.action(async cmd => {
|
||||||
|
await execTask(ciBundlePluginTask)({});
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('plugin:ci-test')
|
||||||
|
.option('--full', 'run all the tests (even stuff that will break)')
|
||||||
|
.description('end-to-end test using bundle in /artifacts')
|
||||||
|
.action(async cmd => {
|
||||||
|
await execTask(ciTestPluginTask)({
|
||||||
|
full: cmd.full,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('plugin:ci-report')
|
||||||
|
.description('Build a report for this whole process')
|
||||||
|
.action(async cmd => {
|
||||||
|
await execTask(ciPluginReportTask)({});
|
||||||
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('plugin:ci-deploy')
|
||||||
|
.description('Publish plugin CI results')
|
||||||
|
.action(async cmd => {
|
||||||
|
await execTask(ciDeployPluginTask)({});
|
||||||
|
});
|
||||||
|
|
||||||
program.on('command:*', () => {
|
program.on('command:*', () => {
|
||||||
console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
|
console.error('Invalid command: %s\nSee --help for a list of available commands.', program.args.join(' '));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import axios from 'axios';
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { Task, TaskRunner } from './task';
|
import { Task, TaskRunner } from './task';
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import execa = require('execa');
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { changeCwdToGrafanaUi, restoreCwd, changeCwdToPackage } from '../utils/cwd';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { useSpinner } from '../utils/useSpinner';
|
import { useSpinner } from '../utils/useSpinner';
|
||||||
import { Task, TaskRunner } from './task';
|
import { Task, TaskRunner } from './task';
|
||||||
@@ -100,4 +99,4 @@ const buildTaskRunner: TaskRunner<PackageBuildOptions> = async ({ scope }) => {
|
|||||||
await Promise.all(scopes.map(s => s()));
|
await Promise.all(scopes.map(s => s()));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildPackageTask = new Task<PackageBuildOptions>('@grafana/ui build', buildTaskRunner);
|
export const buildPackageTask = new Task<PackageBuildOptions>('Package build', buildTaskRunner);
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import execa = require('execa');
|
|||||||
import path = require('path');
|
import path = require('path');
|
||||||
import fs = require('fs');
|
import fs = require('fs');
|
||||||
import glob = require('glob');
|
import glob = require('glob');
|
||||||
import util = require('util');
|
|
||||||
import { Linter, Configuration, RuleFailure } from 'tslint';
|
import { Linter, Configuration, RuleFailure } from 'tslint';
|
||||||
import * as prettier from 'prettier';
|
import * as prettier from 'prettier';
|
||||||
|
|
||||||
@@ -17,7 +16,6 @@ interface PluginBuildOptions {
|
|||||||
|
|
||||||
export const bundlePlugin = useSpinner<PluginBundleOptions>('Compiling...', async options => await bundleFn(options));
|
export const bundlePlugin = useSpinner<PluginBundleOptions>('Compiling...', async options => await bundleFn(options));
|
||||||
|
|
||||||
const readFileAsync = util.promisify(fs.readFile);
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export const clean = useSpinner<void>('Cleaning', async () => await execa('rimraf', [`${process.cwd()}/dist`]));
|
export const clean = useSpinner<void>('Cleaning', async () => await execa('rimraf', [`${process.cwd()}/dist`]));
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Task, TaskRunner } from './task';
|
import { Task, TaskRunner } from './task';
|
||||||
import { pluginBuildRunner } from './plugin.build';
|
import { pluginBuildRunner } from './plugin.build';
|
||||||
import { useSpinner } from '../utils/useSpinner';
|
|
||||||
import { restoreCwd } from '../utils/cwd';
|
import { restoreCwd } from '../utils/cwd';
|
||||||
import { getPluginJson } from '../../config/utils/pluginValidation';
|
import { getPluginJson } from '../../config/utils/pluginValidation';
|
||||||
|
|
||||||
@@ -10,7 +9,8 @@ import path = require('path');
|
|||||||
import fs = require('fs');
|
import fs = require('fs');
|
||||||
|
|
||||||
export interface PluginCIOptions {
|
export interface PluginCIOptions {
|
||||||
dryRun?: boolean;
|
backend?: string;
|
||||||
|
full?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const calcJavascriptSize = (base: string, files?: string[]): number => {
|
const calcJavascriptSize = (base: string, files?: string[]): number => {
|
||||||
@@ -33,46 +33,353 @@ const calcJavascriptSize = (base: string, files?: string[]): number => {
|
|||||||
return size;
|
return size;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pluginCIRunner: TaskRunner<PluginCIOptions> = async ({ dryRun }) => {
|
const getJobFromProcessArgv = () => {
|
||||||
|
const arg = process.argv[2];
|
||||||
|
if (arg && arg.startsWith('plugin:ci-')) {
|
||||||
|
const task = arg.substring('plugin:ci-'.length);
|
||||||
|
if ('build' === task) {
|
||||||
|
if ('--platform' === process.argv[3] && process.argv[4]) {
|
||||||
|
return task + '_' + process.argv[4];
|
||||||
|
}
|
||||||
|
return 'build_nodejs';
|
||||||
|
}
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
return 'unknown_job';
|
||||||
|
};
|
||||||
|
|
||||||
|
const job = process.env.CIRCLE_JOB || getJobFromProcessArgv();
|
||||||
|
|
||||||
|
const getJobFolder = () => {
|
||||||
|
const dir = path.resolve(process.cwd(), 'ci', 'jobs', job);
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCiFolder = () => {
|
||||||
|
const dir = path.resolve(process.cwd(), 'ci');
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
return dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeJobStats = (startTime: number, workDir: string) => {
|
||||||
|
const stats = {
|
||||||
|
job,
|
||||||
|
startTime,
|
||||||
|
endTime: Date.now(),
|
||||||
|
};
|
||||||
|
const f = path.resolve(workDir, 'stats.json');
|
||||||
|
fs.writeFile(f, JSON.stringify(stats, null, 2), err => {
|
||||||
|
if (err) {
|
||||||
|
throw new Error('Unable to stats: ' + f);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. BUILD
|
||||||
|
*
|
||||||
|
* when platform exists it is building backend, otherwise frontend
|
||||||
|
*
|
||||||
|
* Each build writes data:
|
||||||
|
* ~/work/build_xxx/
|
||||||
|
*
|
||||||
|
* Anything that should be put into the final zip file should be put in:
|
||||||
|
* ~/work/build_xxx/dist
|
||||||
|
*/
|
||||||
|
const buildPluginRunner: TaskRunner<PluginCIOptions> = async ({ backend }) => {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const distDir = `${process.cwd()}/dist`;
|
const workDir = getJobFolder();
|
||||||
const artifactsDir = `${process.cwd()}/artifacts`;
|
await execa('rimraf', [workDir]);
|
||||||
await execa('rimraf', [`${process.cwd()}/coverage`]);
|
fs.mkdirSync(workDir);
|
||||||
await execa('rimraf', [artifactsDir]);
|
|
||||||
|
|
||||||
// Do regular build process
|
if (backend) {
|
||||||
await pluginBuildRunner({ coverage: true });
|
console.log('TODO, backend support?');
|
||||||
const elapsed = Date.now() - start;
|
fs.mkdirSync(path.resolve(process.cwd(), 'dist'));
|
||||||
|
const file = path.resolve(process.cwd(), 'dist', `README_${backend}.txt`);
|
||||||
if (!fs.existsSync(artifactsDir)) {
|
fs.writeFile(file, `TODO... build bakend plugin: ${backend}!`, err => {
|
||||||
fs.mkdirSync(artifactsDir);
|
if (err) {
|
||||||
|
throw new Error('Unable to write: ' + file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Do regular build process with coverage
|
||||||
|
await pluginBuildRunner({ coverage: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO? can this typed from @grafana/ui?
|
// Move local folders to the scoped job folder
|
||||||
|
for (const name of ['dist', 'coverage']) {
|
||||||
|
const dir = path.resolve(process.cwd(), name);
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
fs.renameSync(dir, path.resolve(workDir, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeJobStats(start, workDir);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ciBuildPluginTask = new Task<PluginCIOptions>('Build Plugin', buildPluginRunner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. Build Docs
|
||||||
|
*
|
||||||
|
* Take /docs/* and format it into /ci/docs/HTML site
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const buildPluginDocsRunner: TaskRunner<PluginCIOptions> = async () => {
|
||||||
|
const docsSrc = path.resolve(process.cwd(), 'docs');
|
||||||
|
if (!fs.existsSync(docsSrc)) {
|
||||||
|
throw new Error('Docs folder does not exist!');
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
const workDir = getJobFolder();
|
||||||
|
await execa('rimraf', [workDir]);
|
||||||
|
fs.mkdirSync(workDir);
|
||||||
|
|
||||||
|
const docsDest = path.resolve(process.cwd(), 'ci', 'docs');
|
||||||
|
fs.mkdirSync(docsDest);
|
||||||
|
|
||||||
|
const exe = await execa('cp', ['-rv', docsSrc + '/.', docsDest]);
|
||||||
|
console.log(exe.stdout);
|
||||||
|
|
||||||
|
fs.writeFile(path.resolve(docsDest, 'index.html'), `TODO... actually build docs`, err => {
|
||||||
|
if (err) {
|
||||||
|
throw new Error('Unable to docs');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
writeJobStats(start, workDir);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ciBuildPluginDocsTask = new Task<PluginCIOptions>('Build Plugin Docs', buildPluginDocsRunner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. BUNDLE
|
||||||
|
*
|
||||||
|
* Take everything from `~/ci/job/{any}/dist` and
|
||||||
|
* 1. merge it into: `~/ci/dist`
|
||||||
|
* 2. zip it into artifacts in `~/ci/artifacts`
|
||||||
|
* 3. prepare grafana environment in: `~/ci/grafana-test-env`
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const bundlePluginRunner: TaskRunner<PluginCIOptions> = async () => {
|
||||||
|
const start = Date.now();
|
||||||
|
const ciDir = getCiFolder();
|
||||||
|
const artifactsDir = path.resolve(ciDir, 'artifacts');
|
||||||
|
const distDir = path.resolve(ciDir, 'dist');
|
||||||
|
const docsDir = path.resolve(ciDir, 'docs');
|
||||||
|
const grafanaEnvDir = path.resolve(ciDir, 'grafana-test-env');
|
||||||
|
await execa('rimraf', [artifactsDir, distDir, grafanaEnvDir]);
|
||||||
|
fs.mkdirSync(artifactsDir);
|
||||||
|
fs.mkdirSync(distDir);
|
||||||
|
fs.mkdirSync(grafanaEnvDir);
|
||||||
|
|
||||||
|
console.log('Build Dist Folder');
|
||||||
|
|
||||||
|
// 1. Check for a local 'dist' folder
|
||||||
|
const d = path.resolve(process.cwd(), 'dist');
|
||||||
|
if (fs.existsSync(d)) {
|
||||||
|
await execa('cp', ['-rn', d + '/.', distDir]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Look for any 'dist' folders under ci/job/XXX/dist
|
||||||
|
const dirs = fs.readdirSync(path.resolve(ciDir, 'jobs'));
|
||||||
|
for (const j of dirs) {
|
||||||
|
const contents = path.resolve(ciDir, 'jobs', j, 'dist');
|
||||||
|
if (fs.existsSync(contents)) {
|
||||||
|
try {
|
||||||
|
await execa('cp', ['-rn', contents + '/.', distDir]);
|
||||||
|
} catch (er) {
|
||||||
|
throw new Error('Duplicate files found in dist folders');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Building ZIP');
|
||||||
const pluginInfo = getPluginJson(`${distDir}/plugin.json`);
|
const pluginInfo = getPluginJson(`${distDir}/plugin.json`);
|
||||||
const zipName = pluginInfo.id + '-' + pluginInfo.info.version + '.zip';
|
let zipName = pluginInfo.id + '-' + pluginInfo.info.version + '.zip';
|
||||||
const zipFile = path.resolve(artifactsDir, zipName);
|
let zipFile = path.resolve(artifactsDir, zipName);
|
||||||
process.chdir(distDir);
|
process.chdir(distDir);
|
||||||
await execa('zip', ['-r', zipFile, '.']);
|
await execa('zip', ['-r', zipFile, '.']);
|
||||||
restoreCwd();
|
restoreCwd();
|
||||||
|
|
||||||
const stats = {
|
const zipStats = fs.statSync(zipFile);
|
||||||
startTime: start,
|
if (zipStats.size < 100) {
|
||||||
buildTime: elapsed,
|
throw new Error('Invalid zip file: ' + zipFile);
|
||||||
jsSize: calcJavascriptSize(distDir),
|
}
|
||||||
zipSize: fs.statSync(zipFile).size,
|
|
||||||
endTime: Date.now(),
|
const zipInfo: any = {
|
||||||
|
name: zipName,
|
||||||
|
size: zipStats.size,
|
||||||
};
|
};
|
||||||
fs.writeFile(artifactsDir + '/stats.json', JSON.stringify(stats, null, 2), err => {
|
const info: any = {
|
||||||
if (err) {
|
plugin: zipInfo,
|
||||||
throw new Error('Unable to write stats');
|
};
|
||||||
|
try {
|
||||||
|
const exe = await execa('shasum', [zipFile]);
|
||||||
|
const idx = exe.stdout.indexOf(' ');
|
||||||
|
const sha1 = exe.stdout.substring(0, idx);
|
||||||
|
fs.writeFile(zipFile + '.sha1', sha1, err => {});
|
||||||
|
zipInfo.sha1 = sha1;
|
||||||
|
} catch {
|
||||||
|
console.warn('Unable to read SHA1 Checksum');
|
||||||
|
}
|
||||||
|
|
||||||
|
// If docs exist, zip them into artifacts
|
||||||
|
if (fs.existsSync(docsDir)) {
|
||||||
|
zipName = pluginInfo.id + '-' + pluginInfo.info.version + '-docs.zip';
|
||||||
|
zipFile = path.resolve(artifactsDir, zipName);
|
||||||
|
process.chdir(docsDir);
|
||||||
|
await execa('zip', ['-r', zipFile, '.']);
|
||||||
|
restoreCwd();
|
||||||
|
|
||||||
|
const zipStats = fs.statSync(zipFile);
|
||||||
|
const zipInfo: any = {
|
||||||
|
name: zipName,
|
||||||
|
size: zipStats.size,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const exe = await execa('shasum', [zipFile]);
|
||||||
|
const idx = exe.stdout.indexOf(' ');
|
||||||
|
const sha1 = exe.stdout.substring(0, idx);
|
||||||
|
fs.writeFile(zipFile + '.sha1', sha1, err => {});
|
||||||
|
zipInfo.sha1 = sha1;
|
||||||
|
} catch {
|
||||||
|
console.warn('Unable to read SHA1 Checksum');
|
||||||
|
}
|
||||||
|
info.docs = zipInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = path.resolve(artifactsDir, 'info.json');
|
||||||
|
fs.writeFile(p, JSON.stringify(info, null, 2), err => {
|
||||||
|
if (err) {
|
||||||
|
throw new Error('Error writing artifact info: ' + p);
|
||||||
}
|
}
|
||||||
console.log('Stats', stats);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!dryRun) {
|
console.log('Setup Grafan Environment');
|
||||||
console.log('TODO send info to github?');
|
p = path.resolve(grafanaEnvDir, 'plugins', pluginInfo.id);
|
||||||
}
|
fs.mkdirSync(p, { recursive: true });
|
||||||
|
await execa('unzip', [zipFile, '-d', p]);
|
||||||
|
|
||||||
|
// Write the custom settings
|
||||||
|
p = path.resolve(grafanaEnvDir, 'custom.ini');
|
||||||
|
const customIniBody =
|
||||||
|
`# Autogenerated by @grafana/toolkit \n` +
|
||||||
|
`[paths] \n` +
|
||||||
|
`plugins = ${path.resolve(grafanaEnvDir, 'plugins')}\n` +
|
||||||
|
`\n`; // empty line
|
||||||
|
fs.writeFile(p, customIniBody, err => {
|
||||||
|
if (err) {
|
||||||
|
throw new Error('Unable to write: ' + p);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
writeJobStats(start, getJobFolder());
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pluginCITask = new Task<PluginCIOptions>('Plugin CI', pluginCIRunner);
|
export const ciBundlePluginTask = new Task<PluginCIOptions>('Bundle Plugin', bundlePluginRunner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3. Test (end-to-end)
|
||||||
|
*
|
||||||
|
* deploy the zip to a running grafana instance
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const testPluginRunner: TaskRunner<PluginCIOptions> = async ({ full }) => {
|
||||||
|
const start = Date.now();
|
||||||
|
const workDir = getJobFolder();
|
||||||
|
const pluginInfo = getPluginJson(`${process.cwd()}/src/plugin.json`);
|
||||||
|
|
||||||
|
const args = {
|
||||||
|
withCredentials: true,
|
||||||
|
baseURL: process.env.GRAFANA_URL || 'http://localhost:3000/',
|
||||||
|
responseType: 'json',
|
||||||
|
auth: {
|
||||||
|
username: 'admin',
|
||||||
|
password: 'admin',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const axios = require('axios');
|
||||||
|
const frontendSettings = await axios.get('api/frontend/settings', args);
|
||||||
|
|
||||||
|
console.log('Grafana Version: ' + JSON.stringify(frontendSettings.data.buildInfo, null, 2));
|
||||||
|
|
||||||
|
const allPlugins: any[] = await axios.get('api/plugins', args).data;
|
||||||
|
// for (const plugin of allPlugins) {
|
||||||
|
// if (plugin.id === pluginInfo.id) {
|
||||||
|
// console.log('------------');
|
||||||
|
// console.log(plugin);
|
||||||
|
// console.log('------------');
|
||||||
|
// } else {
|
||||||
|
// console.log('Plugin:', plugin.id, plugin.latestVersion);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
console.log('PLUGINS:', allPlugins);
|
||||||
|
|
||||||
|
if (full) {
|
||||||
|
const pluginSettings = await axios.get(`api/plugins/${pluginInfo.id}/settings`, args);
|
||||||
|
console.log('Plugin Info: ' + JSON.stringify(pluginSettings.data, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('TODO puppeteer');
|
||||||
|
|
||||||
|
const elapsed = Date.now() - start;
|
||||||
|
const stats = {
|
||||||
|
job,
|
||||||
|
sha1: `${process.env.CIRCLE_SHA1}`,
|
||||||
|
startTime: start,
|
||||||
|
buildTime: elapsed,
|
||||||
|
endTime: Date.now(),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('TODO Puppeteer Tests', stats);
|
||||||
|
writeJobStats(start, workDir);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ciTestPluginTask = new Task<PluginCIOptions>('Test Plugin (e2e)', testPluginRunner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4. Report
|
||||||
|
*
|
||||||
|
* Create a report from all the previous steps
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const pluginReportRunner: TaskRunner<PluginCIOptions> = async () => {
|
||||||
|
const start = Date.now();
|
||||||
|
const workDir = getJobFolder();
|
||||||
|
const reportDir = path.resolve(process.cwd(), 'ci', 'report');
|
||||||
|
await execa('rimraf', [reportDir]);
|
||||||
|
fs.mkdirSync(reportDir);
|
||||||
|
|
||||||
|
const file = path.resolve(reportDir, `report.txt`);
|
||||||
|
fs.writeFile(file, `TODO... actually make a report (csv etc)`, err => {
|
||||||
|
if (err) {
|
||||||
|
throw new Error('Unable to write: ' + file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('TODO... real report');
|
||||||
|
writeJobStats(start, workDir);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ciPluginReportTask = new Task<PluginCIOptions>('Deploy plugin', pluginReportRunner);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 5. Deploy
|
||||||
|
*
|
||||||
|
* deploy the zip to a running grafana instance
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const deployPluginRunner: TaskRunner<PluginCIOptions> = async () => {
|
||||||
|
console.log('TODO DEPLOY??');
|
||||||
|
console.log(' if PR => write a comment to github with difference ');
|
||||||
|
console.log(' if master | vXYZ ==> upload artifacts to some repo ');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ciDeployPluginTask = new Task<PluginCIOptions>('Deploy plugin', deployPluginRunner);
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import path = require('path');
|
|
||||||
import fs = require('fs');
|
|
||||||
import webpack = require('webpack');
|
import webpack = require('webpack');
|
||||||
import { getWebpackConfig } from '../../../config/webpack.plugin.config';
|
import { getWebpackConfig } from '../../../config/webpack.plugin.config';
|
||||||
import formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
import formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import path = require('path');
|
|
||||||
import * as jestCLI from 'jest-cli';
|
import * as jestCLI from 'jest-cli';
|
||||||
import { useSpinner } from '../../utils/useSpinner';
|
import { useSpinner } from '../../utils/useSpinner';
|
||||||
import { jestConfig } from '../../../config/jest.plugin.config';
|
import { jestConfig } from '../../../config/jest.plugin.config';
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export async function getTeam(team: any): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function addToTeam(team: any, user: any): Promise<any> {
|
export async function addToTeam(team: any, user: any): Promise<any> {
|
||||||
const members = await client.get(`/teams/${team.id}/members`);
|
|
||||||
console.log(`Adding user ${user.name} to team ${team.name}`);
|
console.log(`Adding user ${user.name} to team ${team.name}`);
|
||||||
await client.post(`/teams/${team.id}/members`, { userId: user.id });
|
await client.post(`/teams/${team.id}/members`, { userId: user.id });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import execa = require('execa');
|
import execa = require('execa');
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import { changeCwdToGrafanaUi, restoreCwd, changeCwdToGrafanaToolkit } from '../utils/cwd';
|
|
||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { useSpinner } from '../utils/useSpinner';
|
import { useSpinner } from '../utils/useSpinner';
|
||||||
import { Task, TaskRunner } from './task';
|
import { Task, TaskRunner } from './task';
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { getPluginJson, validatePluginJson } from './pluginValidation';
|
|||||||
describe('pluginValdation', () => {
|
describe('pluginValdation', () => {
|
||||||
describe('plugin.json', () => {
|
describe('plugin.json', () => {
|
||||||
test('missing plugin.json file', () => {
|
test('missing plugin.json file', () => {
|
||||||
expect(() => getPluginJson(`${__dirname}/mocks/missing-plugin-json`)).toThrow('plugin.json file is missing!');
|
expect(() => getPluginJson(`${__dirname}/mocks/missing-plugin.json`)).toThrowError();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import path = require('path');
|
|
||||||
|
|
||||||
// See: packages/grafana-ui/src/types/plugin.ts
|
// See: packages/grafana-ui/src/types/plugin.ts
|
||||||
interface PluginJSONSchema {
|
interface PluginJSONSchema {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -22,15 +20,24 @@ export const validatePluginJson = (pluginJson: any) => {
|
|||||||
if (!pluginJson.info.version) {
|
if (!pluginJson.info.version) {
|
||||||
throw new Error('Plugin info.version is missing in plugin.json');
|
throw new Error('Plugin info.version is missing in plugin.json');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const types = ['panel', 'datasource', 'app'];
|
||||||
|
const type = pluginJson.type;
|
||||||
|
if (!types.includes(type)) {
|
||||||
|
throw new Error('Invalid plugin type in plugin.json: ' + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pluginJson.id.endsWith('-' + type)) {
|
||||||
|
throw new Error('[plugin.json] id should end with: -' + type);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getPluginJson = (root: string = process.cwd()): PluginJSONSchema => {
|
export const getPluginJson = (path: string): PluginJSONSchema => {
|
||||||
let pluginJson;
|
let pluginJson;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pluginJson = require(path.resolve(root, 'src/plugin.json'));
|
pluginJson = require(path);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error('plugin.json file is missing!');
|
throw new Error('Unable to find: ' + path);
|
||||||
}
|
}
|
||||||
|
|
||||||
validatePluginJson(pluginJson);
|
validatePluginJson(pluginJson);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
|||||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||||
|
|
||||||
import * as webpack from 'webpack';
|
import * as webpack from 'webpack';
|
||||||
import { hasThemeStylesheets, getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders';
|
import { getStyleLoaders, getStylesheetEntries, getFileLoaders } from './webpack/loaders';
|
||||||
|
|
||||||
interface WebpackConfigurationOptions {
|
interface WebpackConfigurationOptions {
|
||||||
watch?: boolean;
|
watch?: boolean;
|
||||||
@@ -51,6 +51,7 @@ const getManualChunk = (id: string) => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEntries = () => {
|
const getEntries = () => {
|
||||||
@@ -83,8 +84,8 @@ const getCommonPlugins = (options: WebpackConfigurationOptions) => {
|
|||||||
{ from: '../LICENSE', to: '.' },
|
{ from: '../LICENSE', to: '.' },
|
||||||
{ from: 'img/*', to: '.' },
|
{ from: 'img/*', to: '.' },
|
||||||
{ from: '**/*.json', to: '.' },
|
{ from: '**/*.json', to: '.' },
|
||||||
// { from: '**/*.svg', to: '.' },
|
{ from: '**/*.svg', to: '.' },
|
||||||
// { from: '**/*.png', to: '.' },
|
{ from: '**/*.png', to: '.' },
|
||||||
{ from: '**/*.html', to: '.' },
|
{ from: '**/*.html', to: '.' },
|
||||||
],
|
],
|
||||||
{ logLevel: options.watch ? 'silent' : 'warn' }
|
{ logLevel: options.watch ? 'silent' : 'warn' }
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { getStylesheetEntries, hasThemeStylesheets } from './loaders';
|
|||||||
describe('Loaders', () => {
|
describe('Loaders', () => {
|
||||||
describe('stylesheet helpers', () => {
|
describe('stylesheet helpers', () => {
|
||||||
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
const logSpy = jest.spyOn(console, 'log').mockImplementation();
|
||||||
const errorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
logSpy.mockRestore();
|
logSpy.mockRestore();
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import { getPluginJson } from '../utils/pluginValidation';
|
|
||||||
|
|
||||||
const path = require('path');
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
|
||||||
@@ -122,8 +119,8 @@ export const getFileLoaders = () => {
|
|||||||
? {
|
? {
|
||||||
loader: 'file-loader',
|
loader: 'file-loader',
|
||||||
options: {
|
options: {
|
||||||
outputPath: 'static',
|
outputPath: '/',
|
||||||
name: '[name].[hash:8].[ext]',
|
name: '[path][name].[ext]',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
: // When using single css import images are inlined as base64 URIs in the result bundle
|
: // When using single css import images are inlined as base64 URIs in the result bundle
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
{
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
"include": ["src/**/*.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"rootDirs": ["."],
|
"rootDirs": ["."],
|
||||||
"outDir": "dist/src",
|
"outDir": "dist/src",
|
||||||
"strict": true,
|
"declaration": false,
|
||||||
"alwaysStrict": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"typeRoots": ["./node_modules/@types"],
|
"typeRoots": ["./node_modules/@types"],
|
||||||
"skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors,
|
|
||||||
"removeComments": false,
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"lib": ["es2015", "es2017.string"]
|
"lib": ["es2015", "es2017.string"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "@grafana/ui",
|
"name": "@grafana/ui",
|
||||||
"version": "6.3.0-alpha.36",
|
"version": "6.4.0-alpha.12",
|
||||||
"description": "Grafana Components Library",
|
"description": "Grafana Components Library",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"typescript",
|
"grafana",
|
||||||
"react",
|
"react",
|
||||||
"react-component"
|
"react-component"
|
||||||
],
|
],
|
||||||
@@ -15,18 +15,18 @@
|
|||||||
"storybook:build": "build-storybook -o ./dist/storybook -c .storybook",
|
"storybook:build": "build-storybook -o ./dist/storybook -c .storybook",
|
||||||
"clean": "rimraf ./dist ./compiled",
|
"clean": "rimraf ./dist ./compiled",
|
||||||
"bundle": "rollup -c rollup.config.ts",
|
"bundle": "rollup -c rollup.config.ts",
|
||||||
"build": "grafana-toolkit package:build --scope=ui",
|
"build": "grafana-toolkit package:build --scope=ui"
|
||||||
"postpublish": "npm run clean"
|
|
||||||
},
|
},
|
||||||
"author": "Grafana Labs",
|
"author": "Grafana Labs",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@grafana/data": "^6.4.0-alpha.8",
|
||||||
"@torkelo/react-select": "2.1.1",
|
"@torkelo/react-select": "2.1.1",
|
||||||
"@types/react-color": "2.17.0",
|
"@types/react-color": "2.17.0",
|
||||||
"classnames": "2.2.6",
|
"classnames": "2.2.6",
|
||||||
"d3": "5.9.1",
|
"d3": "5.9.1",
|
||||||
"jquery": "3.4.1",
|
"jquery": "3.4.1",
|
||||||
"lodash": "4.17.11",
|
"lodash": "4.17.14",
|
||||||
"moment": "2.24.0",
|
"moment": "2.24.0",
|
||||||
"papaparse": "4.6.3",
|
"papaparse": "4.6.3",
|
||||||
"react": "16.8.6",
|
"react": "16.8.6",
|
||||||
@@ -77,8 +77,5 @@
|
|||||||
"rollup-plugin-typescript2": "0.19.3",
|
"rollup-plugin-typescript2": "0.19.3",
|
||||||
"rollup-plugin-visualizer": "0.9.2",
|
"rollup-plugin-visualizer": "0.9.2",
|
||||||
"typescript": "3.4.1"
|
"typescript": "3.4.1"
|
||||||
},
|
|
||||||
"resolutions": {
|
|
||||||
"@types/lodash": "4.14.119"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class TableInputCSV extends React.PureComponent<Props, State> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
readCSV = debounce(() => {
|
readCSV: any = debounce(() => {
|
||||||
const { config } = this.props;
|
const { config } = this.props;
|
||||||
const { text } = this.state;
|
const { text } = this.state;
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../tsconfig.json",
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
||||||
"exclude": ["dist", "node_modules"],
|
"exclude": ["dist", "node_modules"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDirs": [".", "stories"],
|
"rootDirs": [".", "stories"],
|
||||||
"module": "esnext",
|
|
||||||
"outDir": "compiled",
|
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "dist",
|
|
||||||
"strict": true,
|
|
||||||
"alwaysStrict": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"typeRoots": ["./node_modules/@types", "types"],
|
"typeRoots": ["./node_modules/@types", "types"],
|
||||||
"skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors,
|
"declarationDir": "dist",
|
||||||
"removeComments": false
|
"outDir": "compiled"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "esnext",
|
||||||
|
"declaration": true,
|
||||||
|
"strict": true,
|
||||||
|
"alwaysStrict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"skipLibCheck": true, // Temp workaround for Duplicate identifier tsc errors,
|
||||||
|
"removeComments": false
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-1
@@ -68,7 +68,10 @@ func (hs *HTTPServer) AddAPIKey(c *models.ReqContext, cmd models.AddApiKeyComman
|
|||||||
if err == models.ErrInvalidApiKeyExpiration {
|
if err == models.ErrInvalidApiKeyExpiration {
|
||||||
return Error(400, err.Error(), nil)
|
return Error(400, err.Error(), nil)
|
||||||
}
|
}
|
||||||
return Error(500, "Failed to add API key", err)
|
if err == models.ErrDuplicateApiKey {
|
||||||
|
return Error(409, err.Error(), nil)
|
||||||
|
}
|
||||||
|
return Error(500, "Failed to add API Key", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := &dtos.NewApiKeyResult{
|
result := &dtos.NewApiKeyResult{
|
||||||
|
|||||||
@@ -278,8 +278,7 @@ func (hs *HTTPServer) PostDashboard(c *m.ReqContext, cmd m.SaveDashboardCommand)
|
|||||||
inFolder := cmd.FolderId > 0
|
inFolder := cmd.FolderId > 0
|
||||||
err := dashboards.MakeUserAdmin(hs.Bus, cmd.OrgId, cmd.UserId, dashboard.Id, !inFolder)
|
err := dashboards.MakeUserAdmin(hs.Bus, cmd.OrgId, cmd.UserId, dashboard.Id, !inFolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hs.log.Error("Could not make user admin", "dashboard", cmd.Result.Title, "user", c.SignedInUser.UserId, "error", err)
|
hs.log.Error("Could not make user admin", "dashboard", dashboard.Title, "user", c.SignedInUser.UserId, "error", err)
|
||||||
return Error(500, "Failed to make user admin of dashboard", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ func (hs *HTTPServer) CreateFolder(c *m.ReqContext, cmd m.CreateFolderCommand) R
|
|||||||
if hs.Cfg.EditorsCanAdmin {
|
if hs.Cfg.EditorsCanAdmin {
|
||||||
if err := dashboards.MakeUserAdmin(hs.Bus, c.OrgId, c.SignedInUser.UserId, cmd.Result.Id, true); err != nil {
|
if err := dashboards.MakeUserAdmin(hs.Bus, c.OrgId, c.SignedInUser.UserId, cmd.Result.Id, true); err != nil {
|
||||||
hs.log.Error("Could not make user admin", "folder", cmd.Result.Title, "user", c.SignedInUser.UserId, "error", err)
|
hs.log.Error("Could not make user admin", "folder", cmd.Result.Title, "user", c.SignedInUser.UserId, "error", err)
|
||||||
return Error(500, "Failed to make user admin of folder", err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,23 +30,6 @@ func GetTeamMembers(c *m.ReqContext) Response {
|
|||||||
return JSON(200, query.Result)
|
return JSON(200, query.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAuthProviderLabel(authModule string) string {
|
|
||||||
switch authModule {
|
|
||||||
case "oauth_github":
|
|
||||||
return "GitHub"
|
|
||||||
case "oauth_google":
|
|
||||||
return "Google"
|
|
||||||
case "oauth_gitlab":
|
|
||||||
return "GitLab"
|
|
||||||
case "oauth_grafana_com", "oauth_grafananet":
|
|
||||||
return "grafana.com"
|
|
||||||
case "ldap", "":
|
|
||||||
return "LDAP"
|
|
||||||
default:
|
|
||||||
return "OAuth"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /api/teams/:teamId/members
|
// POST /api/teams/:teamId/members
|
||||||
func (hs *HTTPServer) AddTeamMember(c *m.ReqContext, cmd m.AddTeamMemberCommand) Response {
|
func (hs *HTTPServer) AddTeamMember(c *m.ReqContext, cmd m.AddTeamMemberCommand) Response {
|
||||||
cmd.OrgId = c.OrgId
|
cmd.OrgId = c.OrgId
|
||||||
|
|||||||
+27
-1
@@ -29,8 +29,11 @@ func getUserUserProfile(userID int64) Response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAuthQuery := m.GetAuthInfoQuery{UserId: userID}
|
getAuthQuery := m.GetAuthInfoQuery{UserId: userID}
|
||||||
|
query.Result.AuthLabels = []string{}
|
||||||
if err := bus.Dispatch(&getAuthQuery); err == nil {
|
if err := bus.Dispatch(&getAuthQuery); err == nil {
|
||||||
query.Result.AuthModule = []string{getAuthQuery.Result.AuthModule}
|
authLabel := GetAuthProviderLabel(getAuthQuery.Result.AuthModule)
|
||||||
|
query.Result.AuthLabels = append(query.Result.AuthLabels, authLabel)
|
||||||
|
query.Result.IsExternal = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON(200, query.Result)
|
return JSON(200, query.Result)
|
||||||
@@ -277,6 +280,12 @@ func searchUser(c *m.ReqContext) (*m.SearchUsersQuery, error) {
|
|||||||
|
|
||||||
for _, user := range query.Result.Users {
|
for _, user := range query.Result.Users {
|
||||||
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
|
user.AvatarUrl = dtos.GetGravatarUrl(user.Email)
|
||||||
|
user.AuthLabels = make([]string, 0)
|
||||||
|
if user.AuthModule != nil && len(user.AuthModule) > 0 {
|
||||||
|
for _, authModule := range user.AuthModule {
|
||||||
|
user.AuthLabels = append(user.AuthLabels, GetAuthProviderLabel(authModule))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query.Result.Page = page
|
query.Result.Page = page
|
||||||
@@ -315,3 +324,20 @@ func ClearHelpFlags(c *m.ReqContext) Response {
|
|||||||
|
|
||||||
return JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
|
return JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetAuthProviderLabel(authModule string) string {
|
||||||
|
switch authModule {
|
||||||
|
case "oauth_github":
|
||||||
|
return "GitHub"
|
||||||
|
case "oauth_google":
|
||||||
|
return "Google"
|
||||||
|
case "oauth_gitlab":
|
||||||
|
return "GitLab"
|
||||||
|
case "oauth_grafana_com", "oauth_grafananet":
|
||||||
|
return "grafana.com"
|
||||||
|
case "ldap", "":
|
||||||
|
return "LDAP"
|
||||||
|
default:
|
||||||
|
return "OAuth"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func EncryptDatasourcePaswords(c utils.CommandLine, sqlStore *sqlstore.SqlStore)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func migrateColumn(session *sqlstore.DBSession, column string) (int, error) {
|
func migrateColumn(session *sqlstore.DBSession, column string) (int, error) {
|
||||||
var rows []map[string]string
|
var rows []map[string][]byte
|
||||||
|
|
||||||
session.Cols("id", column, "secure_json_data")
|
session.Cols("id", column, "secure_json_data")
|
||||||
session.Table("data_source")
|
session.Table("data_source")
|
||||||
@@ -78,7 +78,7 @@ func migrateColumn(session *sqlstore.DBSession, column string) (int, error) {
|
|||||||
return rowsUpdated, errutil.Wrapf(err, "failed to update column: %s", column)
|
return rowsUpdated, errutil.Wrapf(err, "failed to update column: %s", column)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateRows(session *sqlstore.DBSession, rows []map[string]string, passwordFieldName string) (int, error) {
|
func updateRows(session *sqlstore.DBSession, rows []map[string][]byte, passwordFieldName string) (int, error) {
|
||||||
var rowsUpdated int
|
var rowsUpdated int
|
||||||
|
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
@@ -94,7 +94,7 @@ func updateRows(session *sqlstore.DBSession, rows []map[string]string, passwordF
|
|||||||
|
|
||||||
newRow := map[string]interface{}{"secure_json_data": data, passwordFieldName: ""}
|
newRow := map[string]interface{}{"secure_json_data": data, passwordFieldName: ""}
|
||||||
session.Table("data_source")
|
session.Table("data_source")
|
||||||
session.Where("id = ?", row["id"])
|
session.Where("id = ?", string(row["id"]))
|
||||||
// Setting both columns while having value only for secure_json_data should clear the [passwordFieldName] column
|
// Setting both columns while having value only for secure_json_data should clear the [passwordFieldName] column
|
||||||
session.Cols("secure_json_data", passwordFieldName)
|
session.Cols("secure_json_data", passwordFieldName)
|
||||||
|
|
||||||
@@ -108,16 +108,20 @@ func updateRows(session *sqlstore.DBSession, rows []map[string]string, passwordF
|
|||||||
return rowsUpdated, nil
|
return rowsUpdated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUpdatedSecureJSONData(row map[string]string, passwordFieldName string) (map[string]interface{}, error) {
|
func getUpdatedSecureJSONData(row map[string][]byte, passwordFieldName string) (map[string]interface{}, error) {
|
||||||
encryptedPassword, err := util.Encrypt([]byte(row[passwordFieldName]), setting.SecretKey)
|
encryptedPassword, err := util.Encrypt(row[passwordFieldName], setting.SecretKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var secureJSONData map[string]interface{}
|
var secureJSONData map[string]interface{}
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(row["secure_json_data"]), &secureJSONData); err != nil {
|
if len(row["secure_json_data"]) > 0 {
|
||||||
return nil, err
|
if err := json.Unmarshal(row["secure_json_data"], &secureJSONData); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
secureJSONData = map[string]interface{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonFieldName := util.ToCamelCase(passwordFieldName)
|
jsonFieldName := util.ToCamelCase(passwordFieldName)
|
||||||
|
|||||||
@@ -20,19 +20,30 @@ func TestPasswordMigrationCommand(t *testing.T) {
|
|||||||
datasources := []*models.DataSource{
|
datasources := []*models.DataSource{
|
||||||
{Type: "influxdb", Name: "influxdb", Password: "foobar"},
|
{Type: "influxdb", Name: "influxdb", Password: "foobar"},
|
||||||
{Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar"},
|
{Type: "graphite", Name: "graphite", BasicAuthPassword: "foobar"},
|
||||||
{Type: "prometheus", Name: "prometheus", SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{})},
|
{Type: "prometheus", Name: "prometheus"},
|
||||||
|
{Type: "elasticsearch", Name: "elasticsearch", Password: "pwd"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// set required default values
|
// set required default values
|
||||||
for _, ds := range datasources {
|
for _, ds := range datasources {
|
||||||
ds.Created = time.Now()
|
ds.Created = time.Now()
|
||||||
ds.Updated = time.Now()
|
ds.Updated = time.Now()
|
||||||
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{})
|
if ds.Name == "elasticsearch" {
|
||||||
|
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
ds.SecureJsonData = securejsondata.GetEncryptedJsonData(map[string]string{})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := session.Insert(&datasources)
|
_, err := session.Insert(&datasources)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// force secure_json_data to be null to verify that migration can handle that
|
||||||
|
_, err = session.Exec("update data_source set secure_json_data = null where name = 'influxdb'")
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
//run migration
|
//run migration
|
||||||
err = EncryptDatasourcePaswords(&commandstest.FakeCommandLine{}, sqlstore)
|
err = EncryptDatasourcePaswords(&commandstest.FakeCommandLine{}, sqlstore)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
@@ -41,7 +52,7 @@ func TestPasswordMigrationCommand(t *testing.T) {
|
|||||||
var dss []*models.DataSource
|
var dss []*models.DataSource
|
||||||
err = session.SQL("select * from data_source").Find(&dss)
|
err = session.SQL("select * from data_source").Find(&dss)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, len(dss), 3)
|
assert.Equal(t, len(dss), 4)
|
||||||
|
|
||||||
for _, ds := range dss {
|
for _, ds := range dss {
|
||||||
sj := ds.SecureJsonData.Decrypt()
|
sj := ds.SecureJsonData.Decrypt()
|
||||||
@@ -63,5 +74,15 @@ func TestPasswordMigrationCommand(t *testing.T) {
|
|||||||
if ds.Name == "prometheus" {
|
if ds.Name == "prometheus" {
|
||||||
assert.Equal(t, len(sj), 0)
|
assert.Equal(t, len(sj), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ds.Name == "elasticsearch" {
|
||||||
|
assert.Equal(t, ds.Password, "")
|
||||||
|
key, exist := sj["key"]
|
||||||
|
assert.True(t, exist)
|
||||||
|
password, exist := sj["password"]
|
||||||
|
assert.True(t, exist)
|
||||||
|
assert.Equal(t, password, "pwd", "expected password to be moved to securejson")
|
||||||
|
assert.Equal(t, key, "value", "expected existing key to be kept intact in securejson")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ func InstallPlugin(pluginName, version string, c utils.CommandLine) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.Infof("installing %v @ %v\n", pluginName, version)
|
logger.Infof("installing %v @ %v\n", pluginName, version)
|
||||||
logger.Infof("from url: %v\n", downloadURL)
|
logger.Infof("from: %v\n", downloadURL)
|
||||||
logger.Infof("into: %v\n", pluginFolder)
|
logger.Infof("into: %v\n", pluginFolder)
|
||||||
logger.Info("\n")
|
logger.Info("\n")
|
||||||
|
|
||||||
@@ -145,18 +145,27 @@ func downloadFile(pluginName, filePath, url string) (err error) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
resp, err := http.Get(url) // #nosec
|
var bytes []byte
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
if _, err := os.Stat(url); err == nil {
|
||||||
if err != nil {
|
bytes, err = ioutil.ReadFile(url)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp, err := http.Get(url) // #nosec
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
bytes, err = ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return extractFiles(body, pluginName, filePath)
|
return extractFiles(bytes, pluginName, filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractFiles(body []byte, pluginName string, filePath string) error {
|
func extractFiles(body []byte, pluginName string, filePath string) error {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
var ErrInvalidApiKey = errors.New("Invalid API Key")
|
var ErrInvalidApiKey = errors.New("Invalid API Key")
|
||||||
var ErrInvalidApiKeyExpiration = errors.New("Negative value for SecondsToLive")
|
var ErrInvalidApiKeyExpiration = errors.New("Negative value for SecondsToLive")
|
||||||
|
var ErrDuplicateApiKey = errors.New("API Key Organization ID And Name Must Be Unique")
|
||||||
|
|
||||||
type ApiKey struct {
|
type ApiKey struct {
|
||||||
Id int64
|
Id int64
|
||||||
|
|||||||
+4
-2
@@ -216,7 +216,8 @@ type UserProfileDTO struct {
|
|||||||
OrgId int64 `json:"orgId"`
|
OrgId int64 `json:"orgId"`
|
||||||
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
IsGrafanaAdmin bool `json:"isGrafanaAdmin"`
|
||||||
IsDisabled bool `json:"isDisabled"`
|
IsDisabled bool `json:"isDisabled"`
|
||||||
AuthModule []string `json:"authModule"`
|
IsExternal bool `json:"isExternal"`
|
||||||
|
AuthLabels []string `json:"authLabels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserSearchHitDTO struct {
|
type UserSearchHitDTO struct {
|
||||||
@@ -229,7 +230,8 @@ type UserSearchHitDTO struct {
|
|||||||
IsDisabled bool `json:"isDisabled"`
|
IsDisabled bool `json:"isDisabled"`
|
||||||
LastSeenAt time.Time `json:"lastSeenAt"`
|
LastSeenAt time.Time `json:"lastSeenAt"`
|
||||||
LastSeenAtAge string `json:"lastSeenAtAge"`
|
LastSeenAtAge string `json:"lastSeenAtAge"`
|
||||||
AuthModule AuthModuleConversion `json:"authModule"`
|
AuthLabels []string `json:"authLabels"`
|
||||||
|
AuthModule AuthModuleConversion `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserIdDTO struct {
|
type UserIdDTO struct {
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ type IConnection interface {
|
|||||||
type IServer interface {
|
type IServer interface {
|
||||||
Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
|
Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error)
|
||||||
Users([]string) ([]*models.ExternalUserInfo, error)
|
Users([]string) ([]*models.ExternalUserInfo, error)
|
||||||
Auth(string, string) error
|
Bind() error
|
||||||
|
UserBind(string, string) error
|
||||||
Dial() error
|
Dial() error
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
@@ -43,6 +44,23 @@ type Server struct {
|
|||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bind authenticates the connection with the LDAP server
|
||||||
|
// - with the username and password setup in the config
|
||||||
|
// - or, anonymously
|
||||||
|
func (server *Server) Bind() error {
|
||||||
|
if server.shouldAuthAdmin() {
|
||||||
|
if err := server.AuthAdmin(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err := server.Connection.UnauthenticatedBind(server.Config.BindDN)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// UsersMaxRequest is a max amount of users we can request via Users().
|
// UsersMaxRequest is a max amount of users we can request via Users().
|
||||||
// Since many LDAP servers has limitations
|
// Since many LDAP servers has limitations
|
||||||
// on how much items can we return in one request
|
// on how much items can we return in one request
|
||||||
@@ -149,7 +167,7 @@ func (server *Server) Login(query *models.LoginUserQuery) (
|
|||||||
}
|
}
|
||||||
} else if server.shouldSingleBind() {
|
} else if server.shouldSingleBind() {
|
||||||
authAndBind = true
|
authAndBind = true
|
||||||
err = server.Auth(server.singleBindDN(query.Username), query.Password)
|
err = server.UserBind(server.singleBindDN(query.Username), query.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -179,7 +197,7 @@ func (server *Server) Login(query *models.LoginUserQuery) (
|
|||||||
|
|
||||||
if !authAndBind {
|
if !authAndBind {
|
||||||
// Authenticate user
|
// Authenticate user
|
||||||
err = server.Auth(user.AuthId, query.Password)
|
err = server.UserBind(user.AuthId, query.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -380,9 +398,9 @@ func (server *Server) shouldAuthAdmin() bool {
|
|||||||
return server.Config.BindPassword != ""
|
return server.Config.BindPassword != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth authentificates user in LDAP
|
// UserBind authenticates the connection with the LDAP server
|
||||||
func (server *Server) Auth(username, password string) error {
|
func (server *Server) UserBind(username, password string) error {
|
||||||
err := server.auth(username, password)
|
err := server.userBind(username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.log.Error(
|
server.log.Error(
|
||||||
fmt.Sprintf("Cannot authentificate user %s in LDAP", username),
|
fmt.Sprintf("Cannot authentificate user %s in LDAP", username),
|
||||||
@@ -397,7 +415,7 @@ func (server *Server) Auth(username, password string) error {
|
|||||||
|
|
||||||
// AuthAdmin authentificates LDAP admin user
|
// AuthAdmin authentificates LDAP admin user
|
||||||
func (server *Server) AuthAdmin() error {
|
func (server *Server) AuthAdmin() error {
|
||||||
err := server.auth(server.Config.BindDN, server.Config.BindPassword)
|
err := server.userBind(server.Config.BindDN, server.Config.BindPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.log.Error(
|
server.log.Error(
|
||||||
"Cannot authentificate admin user in LDAP",
|
"Cannot authentificate admin user in LDAP",
|
||||||
@@ -410,8 +428,8 @@ func (server *Server) AuthAdmin() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// auth is helper for several types of LDAP authentification
|
// userBind authenticates the connection with the LDAP server
|
||||||
func (server *Server) auth(path, password string) error {
|
func (server *Server) userBind(path, password string) error {
|
||||||
err := server.Connection.Bind(path, password)
|
err := server.Connection.Bind(path, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ldapErr, ok := err.(*ldap.Error); ok {
|
if ldapErr, ok := err.(*ldap.Error); ok {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ func TestLDAPLogin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Convey("Login()", t, func() {
|
Convey("Login()", t, func() {
|
||||||
Convey("Should get invalid credentials when auth fails", func() {
|
Convey("Should get invalid credentials when userBind fails", func() {
|
||||||
connection := &MockConnection{}
|
connection := &MockConnection{}
|
||||||
entry := ldap.Entry{}
|
entry := ldap.Entry{}
|
||||||
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
result := ldap.SearchResult{Entries: []*ldap.Entry{&entry}}
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ func TestLDAPPrivateMethods(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("shouldAuthAdmin()", t, func() {
|
Convey("shouldAuthAdmin()", t, func() {
|
||||||
Convey("it should require admin auth", func() {
|
Convey("it should require admin userBind", func() {
|
||||||
server := &Server{
|
server := &Server{
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
BindPassword: "test",
|
BindPassword: "test",
|
||||||
@@ -156,7 +156,7 @@ func TestLDAPPrivateMethods(t *testing.T) {
|
|||||||
So(result, ShouldBeTrue)
|
So(result, ShouldBeTrue)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("it should not require admin auth", func() {
|
Convey("it should not require admin userBind", func() {
|
||||||
server := &Server{
|
server := &Server{
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
BindPassword: "",
|
BindPassword: "",
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func TestPublicAPI(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Auth()", t, func() {
|
Convey("UserBind()", t, func() {
|
||||||
Convey("Should use provided DN and password", func() {
|
Convey("Should use provided DN and password", func() {
|
||||||
connection := &MockConnection{}
|
connection := &MockConnection{}
|
||||||
var actualUsername, actualPassword string
|
var actualUsername, actualPassword string
|
||||||
@@ -119,7 +119,7 @@ func TestPublicAPI(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dn := "cn=user,ou=users,dc=grafana,dc=org"
|
dn := "cn=user,ou=users,dc=grafana,dc=org"
|
||||||
err := server.Auth(dn, "pwd")
|
err := server.UserBind(dn, "pwd")
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
So(actualUsername, ShouldEqual, dn)
|
So(actualUsername, ShouldEqual, dn)
|
||||||
@@ -141,7 +141,7 @@ func TestPublicAPI(t *testing.T) {
|
|||||||
},
|
},
|
||||||
log: log.New("test-logger"),
|
log: log.New("test-logger"),
|
||||||
}
|
}
|
||||||
err := server.Auth("user", "pwd")
|
err := server.UserBind("user", "pwd")
|
||||||
So(err, ShouldEqual, expected)
|
So(err, ShouldEqual, expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -109,6 +109,10 @@ func (multiples *MultiLDAP) User(login string) (
|
|||||||
|
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
if err := server.Bind(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
users, err := server.Users(search)
|
users, err := server.Users(search)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -142,6 +146,10 @@ func (multiples *MultiLDAP) Users(logins []string) (
|
|||||||
|
|
||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
|
if err := server.Bind(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
users, err := server.Users(logins)
|
users, err := server.Users(logins)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -11,12 +11,15 @@ type MockLDAP struct {
|
|||||||
loginCalledTimes int
|
loginCalledTimes int
|
||||||
closeCalledTimes int
|
closeCalledTimes int
|
||||||
usersCalledTimes int
|
usersCalledTimes int
|
||||||
|
bindCalledTimes int
|
||||||
|
|
||||||
dialErrReturn error
|
dialErrReturn error
|
||||||
|
|
||||||
loginErrReturn error
|
loginErrReturn error
|
||||||
loginReturn *models.ExternalUserInfo
|
loginReturn *models.ExternalUserInfo
|
||||||
|
|
||||||
|
bindErrReturn error
|
||||||
|
|
||||||
usersErrReturn error
|
usersErrReturn error
|
||||||
usersFirstReturn []*models.ExternalUserInfo
|
usersFirstReturn []*models.ExternalUserInfo
|
||||||
usersRestReturn []*models.ExternalUserInfo
|
usersRestReturn []*models.ExternalUserInfo
|
||||||
@@ -40,8 +43,8 @@ func (mock *MockLDAP) Users([]string) ([]*models.ExternalUserInfo, error) {
|
|||||||
return mock.usersRestReturn, mock.usersErrReturn
|
return mock.usersRestReturn, mock.usersErrReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth test fn
|
// UserBind test fn
|
||||||
func (mock *MockLDAP) Auth(string, string) error {
|
func (mock *MockLDAP) UserBind(string, string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +59,11 @@ func (mock *MockLDAP) Close() {
|
|||||||
mock.closeCalledTimes = mock.closeCalledTimes + 1
|
mock.closeCalledTimes = mock.closeCalledTimes + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mock *MockLDAP) Bind() error {
|
||||||
|
mock.bindCalledTimes++
|
||||||
|
return mock.bindErrReturn
|
||||||
|
}
|
||||||
|
|
||||||
// MockMultiLDAP represents testing struct for multildap testing
|
// MockMultiLDAP represents testing struct for multildap testing
|
||||||
type MockMultiLDAP struct {
|
type MockMultiLDAP struct {
|
||||||
LoginCalledTimes int
|
LoginCalledTimes int
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/util/errutil"
|
"github.com/grafana/grafana/pkg/util/errutil"
|
||||||
)
|
)
|
||||||
@@ -185,8 +186,14 @@ func transformMap(i map[interface{}]interface{}) interface{} {
|
|||||||
|
|
||||||
// interpolateValue returns final value after interpolation. At the moment only env var interpolation is done
|
// interpolateValue returns final value after interpolation. At the moment only env var interpolation is done
|
||||||
// here but in the future something like interpolation from file could be also done here.
|
// here but in the future something like interpolation from file could be also done here.
|
||||||
|
// For a literal '$', '$$' can be used to avoid interpolation.
|
||||||
func interpolateValue(val string) string {
|
func interpolateValue(val string) string {
|
||||||
return os.ExpandEnv(val)
|
parts := strings.Split(val, "$$")
|
||||||
|
interpolated := make([]string, len(parts))
|
||||||
|
for i, v := range parts {
|
||||||
|
interpolated[i] = os.ExpandEnv(v)
|
||||||
|
}
|
||||||
|
return strings.Join(interpolated, "$")
|
||||||
}
|
}
|
||||||
|
|
||||||
type interpolated struct {
|
type interpolated struct {
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package values
|
package values
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestValues(t *testing.T) {
|
func TestValues(t *testing.T) {
|
||||||
Convey("Values", t, func() {
|
Convey("Values", t, func() {
|
||||||
os.Setenv("INT", "1")
|
os.Setenv("INT", "1")
|
||||||
os.Setenv("STRING", "test")
|
os.Setenv("STRING", "test")
|
||||||
|
os.Setenv("EMPTYSTRING", "")
|
||||||
os.Setenv("BOOL", "true")
|
os.Setenv("BOOL", "true")
|
||||||
|
|
||||||
Convey("IntValue", func() {
|
Convey("IntValue", func() {
|
||||||
@@ -61,6 +63,24 @@ func TestValues(t *testing.T) {
|
|||||||
So(d.Val.Value(), ShouldEqual, "")
|
So(d.Val.Value(), ShouldEqual, "")
|
||||||
So(d.Val.Raw, ShouldEqual, "")
|
So(d.Val.Raw, ShouldEqual, "")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Convey("empty var should have empty value", func() {
|
||||||
|
unmarshalingTest(`val: $EMPTYSTRING`, d)
|
||||||
|
So(d.Val.Value(), ShouldEqual, "")
|
||||||
|
So(d.Val.Raw, ShouldEqual, "$EMPTYSTRING")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("$$ should be a literal $", func() {
|
||||||
|
unmarshalingTest(`val: $$`, d)
|
||||||
|
So(d.Val.Value(), ShouldEqual, "$")
|
||||||
|
So(d.Val.Raw, ShouldEqual, "$$")
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("$$ should be a literal $ and not expanded within a string", func() {
|
||||||
|
unmarshalingTest(`val: mY,Passwo$$rd`, d)
|
||||||
|
So(d.Val.Value(), ShouldEqual, "mY,Passwo$rd")
|
||||||
|
So(d.Val.Raw, ShouldEqual, "mY,Passwo$$rd")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("BoolValue", func() {
|
Convey("BoolValue", func() {
|
||||||
@@ -199,6 +219,7 @@ func TestValues(t *testing.T) {
|
|||||||
Reset(func() {
|
Reset(func() {
|
||||||
os.Unsetenv("INT")
|
os.Unsetenv("INT")
|
||||||
os.Unsetenv("STRING")
|
os.Unsetenv("STRING")
|
||||||
|
os.Unsetenv("EMPTYSTRING")
|
||||||
os.Unsetenv("BOOL")
|
os.Unsetenv("BOOL")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -37,6 +37,12 @@ func DeleteApiKeyCtx(ctx context.Context, cmd *models.DeleteApiKeyCommand) error
|
|||||||
|
|
||||||
func AddApiKey(cmd *models.AddApiKeyCommand) error {
|
func AddApiKey(cmd *models.AddApiKeyCommand) error {
|
||||||
return inTransaction(func(sess *DBSession) error {
|
return inTransaction(func(sess *DBSession) error {
|
||||||
|
key := models.ApiKey{OrgId: cmd.OrgId, Name: cmd.Name}
|
||||||
|
exists, _ := sess.Get(&key)
|
||||||
|
if exists {
|
||||||
|
return models.ErrDuplicateApiKey
|
||||||
|
}
|
||||||
|
|
||||||
updated := timeNow()
|
updated := timeNow()
|
||||||
var expires *int64 = nil
|
var expires *int64 = nil
|
||||||
if cmd.SecondsToLive > 0 {
|
if cmd.SecondsToLive > 0 {
|
||||||
|
|||||||
@@ -115,3 +115,23 @@ func TestApiKeyDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestApiKeyErrors(t *testing.T) {
|
||||||
|
mockTimeNow()
|
||||||
|
defer resetTimeNow()
|
||||||
|
|
||||||
|
t.Run("Testing API Duplicate Key Errors", func(t *testing.T) {
|
||||||
|
InitTestDB(t)
|
||||||
|
t.Run("Given saved api key", func(t *testing.T) {
|
||||||
|
cmd := models.AddApiKeyCommand{OrgId: 0, Name: "duplicate", Key: "asd"}
|
||||||
|
err := AddApiKey(&cmd)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
t.Run("Add API Key with existing Org ID and Name", func(t *testing.T) {
|
||||||
|
cmd := models.AddApiKeyCommand{OrgId: 0, Name: "duplicate", Key: "asd"}
|
||||||
|
err = AddApiKey(&cmd)
|
||||||
|
assert.EqualError(t, err, models.ErrDuplicateApiKey.Error())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -324,10 +324,6 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedRows = rows.sort((a, b) => {
|
|
||||||
return a.timestamp > b.timestamp ? -1 : 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Meta data to display in status
|
// Meta data to display in status
|
||||||
const meta: LogsMetaItem[] = [];
|
const meta: LogsMetaItem[] = [];
|
||||||
if (_.size(commonLabels) > 0) {
|
if (_.size(commonLabels) > 0) {
|
||||||
@@ -343,7 +339,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel {
|
|||||||
if (limits.length > 0) {
|
if (limits.length > 0) {
|
||||||
meta.push({
|
meta.push({
|
||||||
label: 'Limit',
|
label: 'Limit',
|
||||||
value: `${limits[0].meta.limit} (${sortedRows.length} returned)`,
|
value: `${limits[0].meta.limit} (${rows.length} returned)`,
|
||||||
kind: LogsMetaKind.String,
|
kind: LogsMetaKind.String,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -351,7 +347,7 @@ export function logSeriesToLogsModel(logSeries: DataFrame[]): LogsModel {
|
|||||||
return {
|
return {
|
||||||
hasUniqueLabels,
|
hasUniqueLabels,
|
||||||
meta,
|
meta,
|
||||||
rows: sortedRows,
|
rows,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -418,13 +418,6 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
expect(logsModel.hasUniqueLabels).toBeFalsy();
|
expect(logsModel.hasUniqueLabels).toBeFalsy();
|
||||||
expect(logsModel.rows).toHaveLength(2);
|
expect(logsModel.rows).toHaveLength(2);
|
||||||
expect(logsModel.rows).toMatchObject([
|
expect(logsModel.rows).toMatchObject([
|
||||||
{
|
|
||||||
timestamp: '2019-04-26T14:42:50.991981292Z',
|
|
||||||
entry: 't=2019-04-26T16:42:50+0200 lvl=eror msg="new token…t unhashed token=56d9fdc5c8b7400bd51b060eea8ca9d7',
|
|
||||||
labels: { filename: '/var/log/grafana/grafana.log', job: 'grafana' },
|
|
||||||
logLevel: 'error',
|
|
||||||
uniqueLabels: {},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
timestamp: '2019-04-26T09:28:11.352440161Z',
|
timestamp: '2019-04-26T09:28:11.352440161Z',
|
||||||
entry: 't=2019-04-26T11:05:28+0200 lvl=info msg="Initializing DatasourceCacheService" logger=server',
|
entry: 't=2019-04-26T11:05:28+0200 lvl=info msg="Initializing DatasourceCacheService" logger=server',
|
||||||
@@ -432,6 +425,13 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
logLevel: 'info',
|
logLevel: 'info',
|
||||||
uniqueLabels: {},
|
uniqueLabels: {},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
timestamp: '2019-04-26T14:42:50.991981292Z',
|
||||||
|
entry: 't=2019-04-26T16:42:50+0200 lvl=eror msg="new token…t unhashed token=56d9fdc5c8b7400bd51b060eea8ca9d7',
|
||||||
|
labels: { filename: '/var/log/grafana/grafana.log', job: 'grafana' },
|
||||||
|
logLevel: 'error',
|
||||||
|
uniqueLabels: {},
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(logsModel.series).toHaveLength(2);
|
expect(logsModel.series).toHaveLength(2);
|
||||||
@@ -524,12 +524,6 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
expect(logsModel.hasUniqueLabels).toBeTruthy();
|
expect(logsModel.hasUniqueLabels).toBeTruthy();
|
||||||
expect(logsModel.rows).toHaveLength(3);
|
expect(logsModel.rows).toHaveLength(3);
|
||||||
expect(logsModel.rows).toMatchObject([
|
expect(logsModel.rows).toMatchObject([
|
||||||
{
|
|
||||||
entry: 'INFO 2',
|
|
||||||
labels: { foo: 'bar', baz: '2' },
|
|
||||||
logLevel: LogLevel.error,
|
|
||||||
uniqueLabels: { baz: '2' },
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
entry: 'WARN boooo',
|
entry: 'WARN boooo',
|
||||||
labels: { foo: 'bar', baz: '1' },
|
labels: { foo: 'bar', baz: '1' },
|
||||||
@@ -542,6 +536,12 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
logLevel: LogLevel.error,
|
logLevel: LogLevel.error,
|
||||||
uniqueLabels: { baz: '2' },
|
uniqueLabels: { baz: '2' },
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
entry: 'INFO 2',
|
||||||
|
labels: { foo: 'bar', baz: '2' },
|
||||||
|
logLevel: LogLevel.error,
|
||||||
|
uniqueLabels: { baz: '2' },
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(logsModel.series).toHaveLength(2);
|
expect(logsModel.series).toHaveLength(2);
|
||||||
|
|||||||
@@ -487,11 +487,11 @@ export const getRefIds = (value: any): string[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
||||||
if (a.timeEpochMs < b.timeEpochMs) {
|
if (a.timestamp < b.timestamp) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.timeEpochMs > b.timeEpochMs) {
|
if (a.timestamp > b.timestamp) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,11 +499,11 @@ const sortInAscendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const sortInDescendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
const sortInDescendingOrder = (a: LogRowModel, b: LogRowModel) => {
|
||||||
if (a.timeEpochMs > b.timeEpochMs) {
|
if (a.timestamp > b.timestamp) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a.timeEpochMs < b.timeEpochMs) {
|
if (a.timestamp < b.timestamp) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ export default class AdminEditUserCtrl {
|
|||||||
const user = $scope.user;
|
const user = $scope.user;
|
||||||
|
|
||||||
// External user can not be disabled
|
// External user can not be disabled
|
||||||
if (user.authModule) {
|
if (user.isExternal) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { NavModelSrv } from 'app/core/core';
|
import { NavModelSrv } from 'app/core/core';
|
||||||
|
import tags from 'app/core/utils/tags';
|
||||||
|
|
||||||
export default class AdminListUsersCtrl {
|
export default class AdminListUsersCtrl {
|
||||||
users: any;
|
users: any;
|
||||||
@@ -32,6 +33,8 @@ export default class AdminListUsersCtrl {
|
|||||||
for (let i = 1; i < this.totalPages + 1; i++) {
|
for (let i = 1; i < this.totalPages + 1; i++) {
|
||||||
this.pages.push({ page: i, current: i === this.page });
|
this.pages.push({ page: i, current: i === this.page });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.addUsersAuthLabels();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,10 +43,29 @@ export default class AdminListUsersCtrl {
|
|||||||
this.getUsers();
|
this.getUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
getAuthModule(user: any) {
|
addUsersAuthLabels() {
|
||||||
if (user.authModule && user.authModule.length) {
|
for (const user of this.users) {
|
||||||
return user.authModule[0];
|
user.authLabel = getAuthLabel(user);
|
||||||
|
user.authLabelStyle = getAuthLabelStyle(user.authLabel);
|
||||||
}
|
}
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAuthLabel(user: any) {
|
||||||
|
if (user.authLabels && user.authLabels.length) {
|
||||||
|
return user.authLabels[0];
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAuthLabelStyle(label: string) {
|
||||||
|
if (label === 'LDAP' || !label) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { color, borderColor } = tags.getTagColorsFromName(label);
|
||||||
|
return {
|
||||||
|
'background-color': color,
|
||||||
|
'border-color': borderColor,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -118,48 +118,52 @@
|
|||||||
<h3 class="page-heading">Sessions</h3>
|
<h3 class="page-heading">Sessions</h3>
|
||||||
|
|
||||||
<div class="gf-form-group">
|
<div class="gf-form-group">
|
||||||
<table class="filter-table form-inline">
|
<div class="gf-form">
|
||||||
<thead>
|
<table class="filter-table form-inline">
|
||||||
<tr>
|
<thead>
|
||||||
<th>Last seen</th>
|
<tr>
|
||||||
<th>Logged on</th>
|
<th>Last seen</th>
|
||||||
<th>IP address</th>
|
<th>Logged on</th>
|
||||||
<th>Browser & OS</th>
|
<th>IP address</th>
|
||||||
<th></th>
|
<th>Browser & OS</th>
|
||||||
</tr>
|
<th></th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
<tr ng-repeat="session in sessions">
|
<tbody>
|
||||||
<td ng-if="session.isActive">Now</td>
|
<tr ng-repeat="session in sessions">
|
||||||
<td ng-if="!session.isActive">{{session.seenAt}}</td>
|
<td ng-if="session.isActive">Now</td>
|
||||||
<td>{{session.createdAt}}</td>
|
<td ng-if="!session.isActive">{{session.seenAt}}</td>
|
||||||
<td>{{session.clientIp}}</td>
|
<td>{{session.createdAt}}</td>
|
||||||
<td>{{session.browser}} on {{session.os}} {{session.osVersion}}</td>
|
<td>{{session.clientIp}}</td>
|
||||||
<td>
|
<td>{{session.browser}} on {{session.os}} {{session.osVersion}}</td>
|
||||||
<button class="btn btn-danger btn-small" ng-click="revokeUserSession(session.id)">
|
<td>
|
||||||
<i class="fa fa-power-off"></i>
|
<button class="btn btn-danger btn-small" ng-click="revokeUserSession(session.id)">
|
||||||
</button>
|
<i class="fa fa-power-off"></i>
|
||||||
</td>
|
</button>
|
||||||
</tr>
|
</td>
|
||||||
</tbody>
|
</tr>
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="gf-form-button-row">
|
||||||
|
<button ng-if="sessions.length" class="btn btn-danger" ng-click="revokeAllUserSessions()">
|
||||||
|
Logout user from all devices
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button ng-if="sessions.length" class="btn btn-danger" ng-click="revokeAllUserSessions()">
|
|
||||||
Logout user from all devices
|
<h3 class="page-heading">User status</h3>
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="gf-form-group">
|
<div class="gf-form-group">
|
||||||
<h3 class="page-heading">User status</h3>
|
|
||||||
|
|
||||||
<div class="gf-form-button-row">
|
<div class="gf-form-button-row">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
ng-if="!user.isDisabled"
|
ng-if="!user.isDisabled"
|
||||||
ng-click="disableUser($event)"
|
ng-click="disableUser($event)"
|
||||||
bs-tooltip="user.authModule ? 'External user cannot be activated or deactivated' : ''"
|
bs-tooltip="user.isExternal ? 'External user cannot be enabled or disabled' : ''"
|
||||||
ng-class="{'disabled': user.authModule}"
|
ng-class="{'disabled': user.isExternal}"
|
||||||
>
|
>
|
||||||
Disable
|
Disable
|
||||||
</button>
|
</button>
|
||||||
@@ -168,8 +172,8 @@
|
|||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
ng-if="user.isDisabled"
|
ng-if="user.isDisabled"
|
||||||
ng-click="disableUser($event)"
|
ng-click="disableUser($event)"
|
||||||
bs-tooltip="user.authModule ? 'External user cannot be activated or deactivated' : ''"
|
bs-tooltip="user.isExternal ? 'External user cannot be enabled or disabled' : ''"
|
||||||
ng-class="{'disabled': user.authModule}"
|
ng-class="{'disabled': user.isExternal}"
|
||||||
>
|
>
|
||||||
Enable
|
Enable
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -55,7 +55,9 @@
|
|||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span class="label label-tag" ng-class="{'muted': user.isDisabled}" ng-if="ctrl.getAuthModule(user) === 'ldap'">LDAP</span>
|
<span class="label label-tag" ng-style="user.authLabelStyle" ng-if="user.authLabel">
|
||||||
|
{{user.authLabel}}
|
||||||
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<span class="label label-tag label-tag--gray" ng-if="user.isDisabled">Disabled</span>
|
<span class="label label-tag label-tag--gray" ng-if="user.isDisabled">Disabled</span>
|
||||||
|
|||||||
@@ -72,9 +72,10 @@ export class ResultProcessor {
|
|||||||
const graphInterval = this.state.queryIntervals.intervalMs;
|
const graphInterval = this.state.queryIntervals.intervalMs;
|
||||||
const dataFrame = this.rawData.map(result => guessFieldTypes(toDataFrame(result)));
|
const dataFrame = this.rawData.map(result => guessFieldTypes(toDataFrame(result)));
|
||||||
const newResults = this.rawData ? dataFrameToLogsModel(dataFrame, graphInterval) : null;
|
const newResults = this.rawData ? dataFrameToLogsModel(dataFrame, graphInterval) : null;
|
||||||
|
const sortedNewResults = sortLogsResult(newResults, this.state.refreshInterval);
|
||||||
|
|
||||||
if (this.replacePreviousResults) {
|
if (this.replacePreviousResults) {
|
||||||
return newResults;
|
return sortedNewResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prevLogsResult: LogsModel = this.state.logsResult || { hasUniqueLabels: false, rows: [] };
|
const prevLogsResult: LogsModel = this.state.logsResult || { hasUniqueLabels: false, rows: [] };
|
||||||
@@ -86,17 +87,17 @@ export class ResultProcessor {
|
|||||||
for (const row of rowsInState) {
|
for (const row of rowsInState) {
|
||||||
processedRows.push({ ...row, fresh: false });
|
processedRows.push({ ...row, fresh: false });
|
||||||
}
|
}
|
||||||
for (const row of newResults.rows) {
|
for (const row of sortedNewResults.rows) {
|
||||||
processedRows.push({ ...row, fresh: true });
|
processedRows.push({ ...row, fresh: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const processedSeries = this.mergeGraphResults(newResults.series, seriesInState);
|
const processedSeries = this.mergeGraphResults(sortedNewResults.series, seriesInState);
|
||||||
|
|
||||||
const slice = -1000;
|
const slice = -1000;
|
||||||
const rows = processedRows.slice(slice);
|
const rows = processedRows.slice(slice);
|
||||||
const series = processedSeries.slice(slice);
|
const series = processedSeries.slice(slice);
|
||||||
|
|
||||||
return { ...newResults, rows, series };
|
return { ...sortedNewResults, rows, series };
|
||||||
};
|
};
|
||||||
|
|
||||||
private makeTimeSeriesList = (rawData: any[]) => {
|
private makeTimeSeriesList = (rawData: any[]) => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { variableRegex } from 'app/features/templating/variable';
|
|||||||
import { ScopedVars } from '@grafana/ui';
|
import { ScopedVars } from '@grafana/ui';
|
||||||
import { TimeRange } from '@grafana/data';
|
import { TimeRange } from '@grafana/data';
|
||||||
|
|
||||||
function luceneEscape(value) {
|
function luceneEscape(value: string) {
|
||||||
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
|
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,8 +12,8 @@ export class TemplateSrv {
|
|||||||
variables: any[];
|
variables: any[];
|
||||||
|
|
||||||
private regex = variableRegex;
|
private regex = variableRegex;
|
||||||
private index = {};
|
private index: any = {};
|
||||||
private grafanaVariables = {};
|
private grafanaVariables: any = {};
|
||||||
private builtIns: any = {};
|
private builtIns: any = {};
|
||||||
private timeRange: TimeRange = null;
|
private timeRange: TimeRange = null;
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ export class TemplateSrv {
|
|||||||
this.variables = [];
|
this.variables = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
init(variables, timeRange?: TimeRange) {
|
init(variables: any, timeRange?: TimeRange) {
|
||||||
this.variables = variables;
|
this.variables = variables;
|
||||||
this.timeRange = timeRange;
|
this.timeRange = timeRange;
|
||||||
this.updateIndex();
|
this.updateIndex();
|
||||||
@@ -34,7 +34,7 @@ export class TemplateSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateIndex() {
|
updateIndex() {
|
||||||
const existsOrEmpty = value => value || value === '';
|
const existsOrEmpty = (value: any) => value || value === '';
|
||||||
|
|
||||||
this.index = this.variables.reduce((acc, currentValue) => {
|
this.index = this.variables.reduce((acc, currentValue) => {
|
||||||
if (currentValue.current && (currentValue.current.isNone || existsOrEmpty(currentValue.current.value))) {
|
if (currentValue.current && (currentValue.current.isNone || existsOrEmpty(currentValue.current.value))) {
|
||||||
@@ -64,12 +64,12 @@ export class TemplateSrv {
|
|||||||
this.updateIndex();
|
this.updateIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
variableInitialized(variable) {
|
variableInitialized(variable: any) {
|
||||||
this.index[variable.name] = variable;
|
this.index[variable.name] = variable;
|
||||||
}
|
}
|
||||||
|
|
||||||
getAdhocFilters(datasourceName) {
|
getAdhocFilters(datasourceName: string) {
|
||||||
let filters = [];
|
let filters: any = [];
|
||||||
|
|
||||||
if (this.variables) {
|
if (this.variables) {
|
||||||
for (let i = 0; i < this.variables.length; i++) {
|
for (let i = 0; i < this.variables.length; i++) {
|
||||||
@@ -92,7 +92,7 @@ export class TemplateSrv {
|
|||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
luceneFormat(value) {
|
luceneFormat(value: any) {
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
return luceneEscape(value);
|
return luceneEscape(value);
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ export class TemplateSrv {
|
|||||||
// encode string according to RFC 3986; in contrast to encodeURIComponent()
|
// encode string according to RFC 3986; in contrast to encodeURIComponent()
|
||||||
// also the sub-delims "!", "'", "(", ")" and "*" are encoded;
|
// also the sub-delims "!", "'", "(", ")" and "*" are encoded;
|
||||||
// unicode handling uses UTF-8 as in ECMA-262.
|
// unicode handling uses UTF-8 as in ECMA-262.
|
||||||
encodeURIComponentStrict(str) {
|
encodeURIComponentStrict(str: string) {
|
||||||
return encodeURIComponent(str).replace(/[!'()*]/g, c => {
|
return encodeURIComponent(str).replace(/[!'()*]/g, c => {
|
||||||
return (
|
return (
|
||||||
'%' +
|
'%' +
|
||||||
@@ -120,7 +120,7 @@ export class TemplateSrv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
formatValue(value, format, variable) {
|
formatValue(value: any, format: any, variable: any) {
|
||||||
// for some scopedVars there is no variable
|
// for some scopedVars there is no variable
|
||||||
variable = variable || {};
|
variable = variable || {};
|
||||||
|
|
||||||
@@ -180,11 +180,11 @@ export class TemplateSrv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setGrafanaVariable(name, value) {
|
setGrafanaVariable(name: string, value: any) {
|
||||||
this.grafanaVariables[name] = value;
|
this.grafanaVariables[name] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
getVariableName(expression) {
|
getVariableName(expression: string) {
|
||||||
this.regex.lastIndex = 0;
|
this.regex.lastIndex = 0;
|
||||||
const match = this.regex.exec(expression);
|
const match = this.regex.exec(expression);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
@@ -194,12 +194,12 @@ export class TemplateSrv {
|
|||||||
return variableName;
|
return variableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
variableExists(expression) {
|
variableExists(expression: string) {
|
||||||
const name = this.getVariableName(expression);
|
const name = this.getVariableName(expression);
|
||||||
return name && this.index[name] !== void 0;
|
return name && this.index[name] !== void 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
highlightVariablesAsHtml(str) {
|
highlightVariablesAsHtml(str: string) {
|
||||||
if (!str || !_.isString(str)) {
|
if (!str || !_.isString(str)) {
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ export class TemplateSrv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getAllValue(variable) {
|
getAllValue(variable: any) {
|
||||||
if (variable.allValue) {
|
if (variable.allValue) {
|
||||||
return variable.allValue;
|
return variable.allValue;
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ export class TemplateSrv {
|
|||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(target: string, scopedVars?: ScopedVars, format?: string | Function) {
|
replace(target: string, scopedVars?: ScopedVars, format?: string | Function): any {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
@@ -266,11 +266,11 @@ export class TemplateSrv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isAllValue(value) {
|
isAllValue(value: any) {
|
||||||
return value === '$__all' || (Array.isArray(value) && value[0] === '$__all');
|
return value === '$__all' || (Array.isArray(value) && value[0] === '$__all');
|
||||||
}
|
}
|
||||||
|
|
||||||
replaceWithText(target, scopedVars) {
|
replaceWithText(target: string, scopedVars: ScopedVars) {
|
||||||
if (!target) {
|
if (!target) {
|
||||||
return target;
|
return target;
|
||||||
}
|
}
|
||||||
@@ -278,7 +278,7 @@ export class TemplateSrv {
|
|||||||
let variable;
|
let variable;
|
||||||
this.regex.lastIndex = 0;
|
this.regex.lastIndex = 0;
|
||||||
|
|
||||||
return target.replace(this.regex, (match, var1, var2, fmt2, var3) => {
|
return target.replace(this.regex, (match: any, var1: any, var2: any, fmt2: any, var3: any) => {
|
||||||
if (scopedVars) {
|
if (scopedVars) {
|
||||||
const option = scopedVars[var1 || var2 || var3];
|
const option = scopedVars[var1 || var2 || var3];
|
||||||
if (option) {
|
if (option) {
|
||||||
@@ -297,7 +297,7 @@ export class TemplateSrv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fillVariableValuesForUrl(params, scopedVars?) {
|
fillVariableValuesForUrl(params: any, scopedVars?: ScopedVars) {
|
||||||
_.each(this.variables, variable => {
|
_.each(this.variables, variable => {
|
||||||
if (scopedVars && scopedVars[variable.name] !== void 0) {
|
if (scopedVars && scopedVars[variable.name] !== void 0) {
|
||||||
if (scopedVars[variable.name].skipUrlSync) {
|
if (scopedVars[variable.name].skipUrlSync) {
|
||||||
@@ -313,7 +313,7 @@ export class TemplateSrv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
distributeVariable(value, variable) {
|
distributeVariable(value: any, variable: any) {
|
||||||
value = _.map(value, (val: any, index: number) => {
|
value = _.map(value, (val: any, index: number) => {
|
||||||
if (index !== 0) {
|
if (index !== 0) {
|
||||||
return variable + '=' + val;
|
return variable + '=' + val;
|
||||||
|
|||||||
@@ -16,12 +16,12 @@ export const variableRegexExec = (variableString: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface Variable {
|
export interface Variable {
|
||||||
setValue(option);
|
setValue(option: any): any;
|
||||||
updateOptions();
|
updateOptions(): any;
|
||||||
dependsOn(variable);
|
dependsOn(variable: any): any;
|
||||||
setValueFromUrl(urlValue);
|
setValueFromUrl(urlValue: any): any;
|
||||||
getValueForUrl();
|
getValueForUrl(): any;
|
||||||
getSaveModel();
|
getSaveModel(): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let variableTypes = {};
|
export let variableTypes = {};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Libaries
|
// Libaries
|
||||||
import angular from 'angular';
|
import angular, { IQService, ILocationService, auto, IPromise } from 'angular';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
// Utils & Services
|
// Utils & Services
|
||||||
@@ -19,9 +19,9 @@ export class VariableSrv {
|
|||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private $q,
|
private $q: IQService,
|
||||||
private $location,
|
private $location: ILocationService,
|
||||||
private $injector,
|
private $injector: auto.IInjectorService,
|
||||||
private templateSrv: TemplateSrv,
|
private templateSrv: TemplateSrv,
|
||||||
private timeSrv: TimeSrv
|
private timeSrv: TimeSrv
|
||||||
) {}
|
) {}
|
||||||
@@ -71,7 +71,7 @@ export class VariableSrv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
processVariable(variable, queryParams) {
|
processVariable(variable: any, queryParams: any) {
|
||||||
const dependencies = [];
|
const dependencies = [];
|
||||||
|
|
||||||
for (const otherVariable of this.variables) {
|
for (const otherVariable of this.variables) {
|
||||||
@@ -100,7 +100,8 @@ export class VariableSrv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createVariableFromModel(model) {
|
createVariableFromModel(model: any) {
|
||||||
|
// @ts-ignore
|
||||||
const ctor = variableTypes[model.type].ctor;
|
const ctor = variableTypes[model.type].ctor;
|
||||||
if (!ctor) {
|
if (!ctor) {
|
||||||
throw {
|
throw {
|
||||||
@@ -112,24 +113,24 @@ export class VariableSrv {
|
|||||||
return variable;
|
return variable;
|
||||||
}
|
}
|
||||||
|
|
||||||
addVariable(variable) {
|
addVariable(variable: any) {
|
||||||
this.variables.push(variable);
|
this.variables.push(variable);
|
||||||
this.templateSrv.updateIndex();
|
this.templateSrv.updateIndex();
|
||||||
this.dashboard.updateSubmenuVisibility();
|
this.dashboard.updateSubmenuVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeVariable(variable) {
|
removeVariable(variable: any) {
|
||||||
const index = _.indexOf(this.variables, variable);
|
const index = _.indexOf(this.variables, variable);
|
||||||
this.variables.splice(index, 1);
|
this.variables.splice(index, 1);
|
||||||
this.templateSrv.updateIndex();
|
this.templateSrv.updateIndex();
|
||||||
this.dashboard.updateSubmenuVisibility();
|
this.dashboard.updateSubmenuVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOptions(variable) {
|
updateOptions(variable: any) {
|
||||||
return variable.updateOptions();
|
return variable.updateOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
variableUpdated(variable, emitChangeEvents?) {
|
variableUpdated(variable: any, emitChangeEvents?: any) {
|
||||||
// if there is a variable lock ignore cascading update because we are in a boot up scenario
|
// if there is a variable lock ignore cascading update because we are in a boot up scenario
|
||||||
if (variable.initLock) {
|
if (variable.initLock) {
|
||||||
return this.$q.when();
|
return this.$q.when();
|
||||||
@@ -152,7 +153,7 @@ export class VariableSrv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
selectOptionsForCurrentValue(variable) {
|
selectOptionsForCurrentValue(variable: any) {
|
||||||
let i, y, value, option;
|
let i, y, value, option;
|
||||||
const selected: any = [];
|
const selected: any = [];
|
||||||
|
|
||||||
@@ -176,7 +177,7 @@ export class VariableSrv {
|
|||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
validateVariableSelectionState(variable) {
|
validateVariableSelectionState(variable: any) {
|
||||||
if (!variable.current) {
|
if (!variable.current) {
|
||||||
variable.current = {};
|
variable.current = {};
|
||||||
}
|
}
|
||||||
@@ -221,7 +222,7 @@ export class VariableSrv {
|
|||||||
* @param variable Instance of Variable
|
* @param variable Instance of Variable
|
||||||
* @param urlValue Value of the query parameter
|
* @param urlValue Value of the query parameter
|
||||||
*/
|
*/
|
||||||
setOptionFromUrl(variable: any, urlValue: string | string[]): Promise<any> {
|
setOptionFromUrl(variable: any, urlValue: string | string[]): IPromise<any> {
|
||||||
let promise = this.$q.when();
|
let promise = this.$q.when();
|
||||||
|
|
||||||
if (variable.refresh) {
|
if (variable.refresh) {
|
||||||
@@ -268,7 +269,7 @@ export class VariableSrv {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setOptionAsCurrent(variable, option) {
|
setOptionAsCurrent(variable: any, option: any) {
|
||||||
variable.current = _.cloneDeep(option);
|
variable.current = _.cloneDeep(option);
|
||||||
|
|
||||||
if (_.isArray(variable.current.text) && variable.current.text.length > 0) {
|
if (_.isArray(variable.current.text) && variable.current.text.length > 0) {
|
||||||
@@ -298,7 +299,7 @@ export class VariableSrv {
|
|||||||
this.$location.search(params);
|
this.$location.search(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
setAdhocFilter(options) {
|
setAdhocFilter(options: any) {
|
||||||
let variable: any = _.find(this.variables, {
|
let variable: any = _.find(this.variables, {
|
||||||
type: 'adhoc',
|
type: 'adhoc',
|
||||||
datasource: options.datasource,
|
datasource: options.datasource,
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ export class UsersActionBar extends PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state: any) {
|
||||||
return {
|
return {
|
||||||
searchQuery: getUsersSearchQuery(state.users),
|
searchQuery: getUsersSearchQuery(state.users),
|
||||||
pendingInvitesCount: getInviteesCount(state.users),
|
pendingInvitesCount: getInviteesCount(state.users),
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export interface State {
|
|||||||
export class UsersListPage extends PureComponent<Props, State> {
|
export class UsersListPage extends PureComponent<Props, State> {
|
||||||
externalUserMngInfoHtml: string;
|
externalUserMngInfoHtml: string;
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
if (this.props.externalUserMngInfo) {
|
if (this.props.externalUserMngInfo) {
|
||||||
@@ -59,13 +59,13 @@ export class UsersListPage extends PureComponent<Props, State> {
|
|||||||
return await this.props.loadInvitees();
|
return await this.props.loadInvitees();
|
||||||
}
|
}
|
||||||
|
|
||||||
onRoleChange = (role, user) => {
|
onRoleChange = (role: string, user: OrgUser) => {
|
||||||
const updatedUser = { ...user, role: role };
|
const updatedUser = { ...user, role: role };
|
||||||
|
|
||||||
this.props.updateUser(updatedUser);
|
this.props.updateUser(updatedUser);
|
||||||
};
|
};
|
||||||
|
|
||||||
onRemoveUser = user => {
|
onRemoveUser = (user: OrgUser) => {
|
||||||
appEvents.emit('confirm-modal', {
|
appEvents.emit('confirm-modal', {
|
||||||
title: 'Delete',
|
title: 'Delete',
|
||||||
text: 'Are you sure you want to delete user ' + user.login + '?',
|
text: 'Are you sure you want to delete user ' + user.login + '?',
|
||||||
@@ -119,7 +119,7 @@ export class UsersListPage extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state: any) {
|
||||||
return {
|
return {
|
||||||
navModel: getNavModel(state.navIndex, 'users'),
|
navModel: getNavModel(state.navIndex, 'users'),
|
||||||
users: getUsers(state.users),
|
users: getUsers(state.users),
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export const getUsers = state => {
|
import { UsersState } from 'app/types';
|
||||||
|
|
||||||
|
export const getUsers = (state: UsersState) => {
|
||||||
const regex = new RegExp(state.searchQuery, 'i');
|
const regex = new RegExp(state.searchQuery, 'i');
|
||||||
|
|
||||||
return state.users.filter(user => {
|
return state.users.filter(user => {
|
||||||
@@ -6,7 +8,7 @@ export const getUsers = state => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInvitees = state => {
|
export const getInvitees = (state: UsersState) => {
|
||||||
const regex = new RegExp(state.searchQuery, 'i');
|
const regex = new RegExp(state.searchQuery, 'i');
|
||||||
|
|
||||||
return state.invitees.filter(invitee => {
|
return state.invitees.filter(invitee => {
|
||||||
@@ -14,5 +16,5 @@ export const getInvitees = state => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getInviteesCount = state => state.invitees.length;
|
export const getInviteesCount = (state: UsersState) => state.invitees.length;
|
||||||
export const getUsersSearchQuery = state => state.searchQuery;
|
export const getUsersSearchQuery = (state: UsersState) => state.searchQuery;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import DatasourceSrv from 'app/features/plugins/datasource_srv';
|
||||||
|
import CloudWatchDatasource from './datasource';
|
||||||
export class CloudWatchConfigCtrl {
|
export class CloudWatchConfigCtrl {
|
||||||
static templateUrl = 'partials/config.html';
|
static templateUrl = 'partials/config.html';
|
||||||
current: any;
|
current: any;
|
||||||
@@ -8,7 +10,7 @@ export class CloudWatchConfigCtrl {
|
|||||||
secretKeyExist = false;
|
secretKeyExist = false;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, datasourceSrv) {
|
constructor($scope: any, datasourceSrv: DatasourceSrv) {
|
||||||
this.current.jsonData.timeField = this.current.jsonData.timeField || '@timestamp';
|
this.current.jsonData.timeField = this.current.jsonData.timeField || '@timestamp';
|
||||||
this.current.jsonData.authType = this.current.jsonData.authType || 'credentials';
|
this.current.jsonData.authType = this.current.jsonData.authType || 'credentials';
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ export class CloudWatchConfigCtrl {
|
|||||||
{ name: 'ARN', value: 'arn' },
|
{ name: 'ARN', value: 'arn' },
|
||||||
];
|
];
|
||||||
|
|
||||||
indexPatternTypes = [
|
indexPatternTypes: any = [
|
||||||
{ name: 'No pattern', value: undefined },
|
{ name: 'No pattern', value: undefined },
|
||||||
{ name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH' },
|
{ name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH' },
|
||||||
{ name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD' },
|
{ name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD' },
|
||||||
@@ -71,14 +73,14 @@ export class CloudWatchConfigCtrl {
|
|||||||
getRegions() {
|
getRegions() {
|
||||||
this.datasourceSrv
|
this.datasourceSrv
|
||||||
.loadDatasource(this.current.name)
|
.loadDatasource(this.current.name)
|
||||||
.then(ds => {
|
.then((ds: CloudWatchDatasource) => {
|
||||||
return ds.getRegions();
|
return ds.getRegions();
|
||||||
})
|
})
|
||||||
.then(
|
.then(
|
||||||
regions => {
|
(regions: any) => {
|
||||||
this.regions = _.map(regions, 'value');
|
this.regions = _.map(regions, 'value');
|
||||||
},
|
},
|
||||||
err => {
|
(err: any) => {
|
||||||
console.error('failed to get latest regions');
|
console.error('failed to get latest regions');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import angular from 'angular';
|
import angular, { IQService } from 'angular';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { dateMath } from '@grafana/data';
|
import { dateMath } from '@grafana/data';
|
||||||
import kbn from 'app/core/utils/kbn';
|
import kbn from 'app/core/utils/kbn';
|
||||||
import { CloudWatchQuery } from './types';
|
import { CloudWatchQuery } from './types';
|
||||||
import { DataSourceApi, DataQueryRequest, DataSourceInstanceSettings } from '@grafana/ui';
|
import { DataSourceApi, DataQueryRequest, DataSourceInstanceSettings, ScopedVars } from '@grafana/ui';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
|
||||||
@@ -18,7 +18,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(
|
constructor(
|
||||||
private instanceSettings: DataSourceInstanceSettings,
|
private instanceSettings: DataSourceInstanceSettings,
|
||||||
private $q,
|
private $q: IQService,
|
||||||
private backendSrv: BackendSrv,
|
private backendSrv: BackendSrv,
|
||||||
private templateSrv: TemplateSrv,
|
private templateSrv: TemplateSrv,
|
||||||
private timeSrv: TimeSrv
|
private timeSrv: TimeSrv
|
||||||
@@ -96,7 +96,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
return this.performTimeSeriesQuery(request);
|
return this.performTimeSeriesQuery(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
getPeriod(target, options, now?) {
|
getPeriod(target: any, options: any, now?: number) {
|
||||||
const start = this.convertToCloudWatchTime(options.range.from, false);
|
const start = this.convertToCloudWatchTime(options.range.from, false);
|
||||||
const end = this.convertToCloudWatchTime(options.range.to, true);
|
const end = this.convertToCloudWatchTime(options.range.to, true);
|
||||||
now = Math.round((now || Date.now()) / 1000);
|
now = Math.round((now || Date.now()) / 1000);
|
||||||
@@ -142,8 +142,8 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
return period;
|
return period;
|
||||||
}
|
}
|
||||||
|
|
||||||
performTimeSeriesQuery(request) {
|
performTimeSeriesQuery(request: any) {
|
||||||
return this.awsRequest('/api/tsdb/query', request).then(res => {
|
return this.awsRequest('/api/tsdb/query', request).then((res: any) => {
|
||||||
const data = [];
|
const data = [];
|
||||||
|
|
||||||
if (res.results) {
|
if (res.results) {
|
||||||
@@ -165,7 +165,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
transformSuggestDataFromTable(suggestData) {
|
transformSuggestDataFromTable(suggestData: any) {
|
||||||
return _.map(suggestData.results['metricFindQuery'].tables[0].rows, v => {
|
return _.map(suggestData.results['metricFindQuery'].tables[0].rows, v => {
|
||||||
return {
|
return {
|
||||||
text: v[0],
|
text: v[0],
|
||||||
@@ -174,7 +174,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doMetricQueryRequest(subtype, parameters) {
|
doMetricQueryRequest(subtype: any, parameters: any) {
|
||||||
const range = this.timeSrv.timeRange();
|
const range = this.timeSrv.timeRange();
|
||||||
return this.awsRequest('/api/tsdb/query', {
|
return this.awsRequest('/api/tsdb/query', {
|
||||||
from: range.from.valueOf().toString(),
|
from: range.from.valueOf().toString(),
|
||||||
@@ -192,7 +192,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
parameters
|
parameters
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
}).then(r => {
|
}).then((r: any) => {
|
||||||
return this.transformSuggestDataFromTable(r);
|
return this.transformSuggestDataFromTable(r);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -205,21 +205,27 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
return this.doMetricQueryRequest('namespaces', null);
|
return this.doMetricQueryRequest('namespaces', null);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMetrics(namespace, region) {
|
getMetrics(namespace: string, region: string) {
|
||||||
return this.doMetricQueryRequest('metrics', {
|
return this.doMetricQueryRequest('metrics', {
|
||||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||||
namespace: this.templateSrv.replace(namespace),
|
namespace: this.templateSrv.replace(namespace),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getDimensionKeys(namespace, region) {
|
getDimensionKeys(namespace: string, region: string) {
|
||||||
return this.doMetricQueryRequest('dimension_keys', {
|
return this.doMetricQueryRequest('dimension_keys', {
|
||||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||||
namespace: this.templateSrv.replace(namespace),
|
namespace: this.templateSrv.replace(namespace),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getDimensionValues(region, namespace, metricName, dimensionKey, filterDimensions) {
|
getDimensionValues(
|
||||||
|
region: string,
|
||||||
|
namespace: string,
|
||||||
|
metricName: string,
|
||||||
|
dimensionKey: string,
|
||||||
|
filterDimensions: {}
|
||||||
|
) {
|
||||||
return this.doMetricQueryRequest('dimension_values', {
|
return this.doMetricQueryRequest('dimension_values', {
|
||||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||||
namespace: this.templateSrv.replace(namespace),
|
namespace: this.templateSrv.replace(namespace),
|
||||||
@@ -229,14 +235,14 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getEbsVolumeIds(region, instanceId) {
|
getEbsVolumeIds(region: string, instanceId: string) {
|
||||||
return this.doMetricQueryRequest('ebs_volume_ids', {
|
return this.doMetricQueryRequest('ebs_volume_ids', {
|
||||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||||
instanceId: this.templateSrv.replace(instanceId),
|
instanceId: this.templateSrv.replace(instanceId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getEc2InstanceAttribute(region, attributeName, filters) {
|
getEc2InstanceAttribute(region: string, attributeName: string, filters: any) {
|
||||||
return this.doMetricQueryRequest('ec2_instance_attribute', {
|
return this.doMetricQueryRequest('ec2_instance_attribute', {
|
||||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||||
attributeName: this.templateSrv.replace(attributeName),
|
attributeName: this.templateSrv.replace(attributeName),
|
||||||
@@ -244,7 +250,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getResourceARNs(region, resourceType, tags) {
|
getResourceARNs(region: string, resourceType: string, tags: any) {
|
||||||
return this.doMetricQueryRequest('resource_arns', {
|
return this.doMetricQueryRequest('resource_arns', {
|
||||||
region: this.templateSrv.replace(this.getActualRegion(region)),
|
region: this.templateSrv.replace(this.getActualRegion(region)),
|
||||||
resourceType: this.templateSrv.replace(resourceType),
|
resourceType: this.templateSrv.replace(resourceType),
|
||||||
@@ -252,7 +258,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
metricFindQuery(query) {
|
metricFindQuery(query: string) {
|
||||||
let region;
|
let region;
|
||||||
let namespace;
|
let namespace;
|
||||||
let metricName;
|
let metricName;
|
||||||
@@ -324,7 +330,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
return this.$q.when([]);
|
return this.$q.when([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationQuery(options) {
|
annotationQuery(options: any) {
|
||||||
const annotation = options.annotation;
|
const annotation = options.annotation;
|
||||||
const statistics = _.map(annotation.statistics, s => {
|
const statistics = _.map(annotation.statistics, s => {
|
||||||
return this.templateSrv.replace(s);
|
return this.templateSrv.replace(s);
|
||||||
@@ -359,7 +365,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
parameters
|
parameters
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
}).then(r => {
|
}).then((r: any) => {
|
||||||
return _.map(r.results['annotationQuery'].tables[0].rows, v => {
|
return _.map(r.results['annotationQuery'].tables[0].rows, v => {
|
||||||
return {
|
return {
|
||||||
annotation: annotation,
|
annotation: annotation,
|
||||||
@@ -372,7 +378,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
targetContainsTemplate(target) {
|
targetContainsTemplate(target: any) {
|
||||||
return (
|
return (
|
||||||
this.templateSrv.variableExists(target.region) ||
|
this.templateSrv.variableExists(target.region) ||
|
||||||
this.templateSrv.variableExists(target.namespace) ||
|
this.templateSrv.variableExists(target.namespace) ||
|
||||||
@@ -395,14 +401,14 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
awsRequest(url, data) {
|
awsRequest(url: string, data: any) {
|
||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: url,
|
url,
|
||||||
data: data,
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.backendSrv.datasourceRequest(options).then(result => {
|
return this.backendSrv.datasourceRequest(options).then((result: any) => {
|
||||||
return result.data;
|
return result.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -411,14 +417,14 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
return this.defaultRegion;
|
return this.defaultRegion;
|
||||||
}
|
}
|
||||||
|
|
||||||
getActualRegion(region) {
|
getActualRegion(region: string) {
|
||||||
if (region === 'default' || _.isEmpty(region)) {
|
if (region === 'default' || _.isEmpty(region)) {
|
||||||
return this.getDefaultRegion();
|
return this.getDefaultRegion();
|
||||||
}
|
}
|
||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
getExpandedVariables(target, dimensionKey, variable, templateSrv) {
|
getExpandedVariables(target: any, dimensionKey: any, variable: any, templateSrv: TemplateSrv) {
|
||||||
/* if the all checkbox is marked we should add all values to the targets */
|
/* if the all checkbox is marked we should add all values to the targets */
|
||||||
const allSelected: any = _.find(variable.options, { selected: true, text: 'All' });
|
const allSelected: any = _.find(variable.options, { selected: true, text: 'All' });
|
||||||
const selectedVariables = _.filter(variable.options, v => {
|
const selectedVariables = _.filter(variable.options, v => {
|
||||||
@@ -430,7 +436,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
});
|
});
|
||||||
const currentVariables = !_.isArray(variable.current.value)
|
const currentVariables = !_.isArray(variable.current.value)
|
||||||
? [variable.current]
|
? [variable.current]
|
||||||
: variable.current.value.map(v => {
|
: variable.current.value.map((v: any) => {
|
||||||
return {
|
return {
|
||||||
text: v,
|
text: v,
|
||||||
value: v,
|
value: v,
|
||||||
@@ -440,9 +446,9 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
selectedVariables.some((s: any) => {
|
selectedVariables.some((s: any) => {
|
||||||
return s.value === currentVariables[0].value;
|
return s.value === currentVariables[0].value;
|
||||||
}) || currentVariables[0].value === '$__all';
|
}) || currentVariables[0].value === '$__all';
|
||||||
return (useSelectedVariables ? selectedVariables : currentVariables).map(v => {
|
return (useSelectedVariables ? selectedVariables : currentVariables).map((v: any) => {
|
||||||
const t = angular.copy(target);
|
const t = angular.copy(target);
|
||||||
const scopedVar = {};
|
const scopedVar: any = {};
|
||||||
scopedVar[variable.name] = v;
|
scopedVar[variable.name] = v;
|
||||||
t.refId = target.refId + '_' + v.value;
|
t.refId = target.refId + '_' + v.value;
|
||||||
t.dimensions[dimensionKey] = templateSrv.replace(t.dimensions[dimensionKey], scopedVar);
|
t.dimensions[dimensionKey] = templateSrv.replace(t.dimensions[dimensionKey], scopedVar);
|
||||||
@@ -455,7 +461,7 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
expandTemplateVariable(targets, scopedVars, templateSrv) {
|
expandTemplateVariable(targets: any, scopedVars: ScopedVars, templateSrv: TemplateSrv) {
|
||||||
// Datasource and template srv logic uber-complected. This should be cleaned up.
|
// Datasource and template srv logic uber-complected. This should be cleaned up.
|
||||||
return _.chain(targets)
|
return _.chain(targets)
|
||||||
.map(target => {
|
.map(target => {
|
||||||
@@ -480,15 +486,15 @@ export default class CloudWatchDatasource extends DataSourceApi<CloudWatchQuery>
|
|||||||
.value();
|
.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
convertToCloudWatchTime(date, roundUp) {
|
convertToCloudWatchTime(date: any, roundUp: any) {
|
||||||
if (_.isString(date)) {
|
if (_.isString(date)) {
|
||||||
date = dateMath.parse(date, roundUp);
|
date = dateMath.parse(date, roundUp);
|
||||||
}
|
}
|
||||||
return Math.round(date.valueOf() / 1000);
|
return Math.round(date.valueOf() / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
convertDimensionFormat(dimensions, scopedVars) {
|
convertDimensionFormat(dimensions: any, scopedVars: ScopedVars) {
|
||||||
const convertedDimensions = {};
|
const convertedDimensions: any = {};
|
||||||
_.each(dimensions, (value, key) => {
|
_.each(dimensions, (value, key) => {
|
||||||
convertedDimensions[this.templateSrv.replace(key, scopedVars)] = this.templateSrv.replace(value, scopedVars);
|
convertedDimensions[this.templateSrv.replace(key, scopedVars)] = this.templateSrv.replace(value, scopedVars);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import './query_parameter_ctrl';
|
import './query_parameter_ctrl';
|
||||||
import { QueryCtrl } from 'app/plugins/sdk';
|
import { QueryCtrl } from 'app/plugins/sdk';
|
||||||
|
import { auto } from 'angular';
|
||||||
|
|
||||||
export class CloudWatchQueryCtrl extends QueryCtrl {
|
export class CloudWatchQueryCtrl extends QueryCtrl {
|
||||||
static templateUrl = 'partials/query.editor.html';
|
static templateUrl = 'partials/query.editor.html';
|
||||||
@@ -7,7 +8,7 @@ export class CloudWatchQueryCtrl extends QueryCtrl {
|
|||||||
aliasSyntax: string;
|
aliasSyntax: string;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, $injector) {
|
constructor($scope: any, $injector: auto.IInjectorService) {
|
||||||
super($scope, $injector);
|
super($scope, $injector);
|
||||||
this.aliasSyntax = '{{metric}} {{stat}} {{namespace}} {{region}} {{<dimension name>}}';
|
this.aliasSyntax = '{{metric}} {{stat}} {{namespace}} {{region}} {{<dimension name>}}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import angular from 'angular';
|
import angular, { IQService } from 'angular';
|
||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
import DatasourceSrv from 'app/features/plugins/datasource_srv';
|
||||||
|
|
||||||
export class CloudWatchQueryParameterCtrl {
|
export class CloudWatchQueryParameterCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, templateSrv, uiSegmentSrv, datasourceSrv, $q) {
|
constructor($scope: any, templateSrv: TemplateSrv, uiSegmentSrv: any, datasourceSrv: DatasourceSrv, $q: IQService) {
|
||||||
$scope.init = () => {
|
$scope.init = () => {
|
||||||
const target = $scope.target;
|
const target = $scope.target;
|
||||||
target.namespace = target.namespace || '';
|
target.namespace = target.namespace || '';
|
||||||
@@ -69,7 +71,7 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.statSegmentChanged = (segment, index) => {
|
$scope.statSegmentChanged = (segment: any, index: number) => {
|
||||||
if (segment.value === $scope.removeStatSegment.value) {
|
if (segment.value === $scope.removeStatSegment.value) {
|
||||||
$scope.statSegments.splice(index, 1);
|
$scope.statSegments.splice(index, 1);
|
||||||
} else {
|
} else {
|
||||||
@@ -91,7 +93,7 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
$scope.onChange();
|
$scope.onChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.ensurePlusButton = segments => {
|
$scope.ensurePlusButton = (segments: any) => {
|
||||||
const count = segments.length;
|
const count = segments.length;
|
||||||
const lastSegment = segments[Math.max(count - 1, 0)];
|
const lastSegment = segments[Math.max(count - 1, 0)];
|
||||||
|
|
||||||
@@ -100,7 +102,7 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getDimSegments = (segment, $index) => {
|
$scope.getDimSegments = (segment: any, $index: number) => {
|
||||||
if (segment.type === 'operator') {
|
if (segment.type === 'operator') {
|
||||||
return $q.when([]);
|
return $q.when([]);
|
||||||
}
|
}
|
||||||
@@ -130,7 +132,7 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.dimSegmentChanged = (segment, index) => {
|
$scope.dimSegmentChanged = (segment: any, index: number) => {
|
||||||
$scope.dimSegments[index] = segment;
|
$scope.dimSegments[index] = segment;
|
||||||
|
|
||||||
if (segment.value === $scope.removeDimSegment.value) {
|
if (segment.value === $scope.removeDimSegment.value) {
|
||||||
@@ -148,7 +150,7 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$scope.syncDimSegmentsWithModel = () => {
|
$scope.syncDimSegmentsWithModel = () => {
|
||||||
const dims = {};
|
const dims: any = {};
|
||||||
const length = $scope.dimSegments.length;
|
const length = $scope.dimSegments.length;
|
||||||
|
|
||||||
for (let i = 0; i < length - 2; i += 3) {
|
for (let i = 0; i < length - 2; i += 3) {
|
||||||
@@ -165,7 +167,7 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
$scope.getRegions = () => {
|
$scope.getRegions = () => {
|
||||||
return $scope.datasource
|
return $scope.datasource
|
||||||
.metricFindQuery('regions()')
|
.metricFindQuery('regions()')
|
||||||
.then(results => {
|
.then((results: any) => {
|
||||||
results.unshift({ text: 'default' });
|
results.unshift({ text: 'default' });
|
||||||
return results;
|
return results;
|
||||||
})
|
})
|
||||||
@@ -197,8 +199,8 @@ export class CloudWatchQueryParameterCtrl {
|
|||||||
$scope.onChange();
|
$scope.onChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.transformToSegments = addTemplateVars => {
|
$scope.transformToSegments = (addTemplateVars: any) => {
|
||||||
return results => {
|
return (results: any) => {
|
||||||
const segments = _.map(results, segment => {
|
const segments = _.map(results, segment => {
|
||||||
return uiSegmentSrv.newSegment({
|
return uiSegmentSrv.newSegment({
|
||||||
value: segment.text,
|
value: segment.text,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.ds = new CloudWatchDatasource(instanceSettings, {}, backendSrv, templateSrv, timeSrv);
|
ctx.ds = new CloudWatchDatasource(instanceSettings, {} as any, backendSrv, templateSrv, timeSrv);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When performing CloudWatch query', () => {
|
describe('When performing CloudWatch query', () => {
|
||||||
@@ -56,7 +56,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = {
|
const response: any = {
|
||||||
timings: [null],
|
timings: [null],
|
||||||
results: {
|
results: {
|
||||||
A: {
|
A: {
|
||||||
@@ -156,7 +156,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return series list', done => {
|
it('should return series list', done => {
|
||||||
ctx.ds.query(query).then(result => {
|
ctx.ds.query(query).then((result: any) => {
|
||||||
expect(result.data[0].target).toBe(response.results.A.series[0].name);
|
expect(result.data[0].target).toBe(response.results.A.series[0].name);
|
||||||
expect(result.data[0].datapoints[0][0]).toBe(response.results.A.series[0].points[0][0]);
|
expect(result.data[0].datapoints[0][0]).toBe(response.results.A.series[0].points[0][0]);
|
||||||
done();
|
done();
|
||||||
@@ -204,7 +204,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.ds.query(query).then(result => {
|
ctx.ds.query(query).then((result: any) => {
|
||||||
expect(requestParams.queries[0].region).toBe(instanceSettings.jsonData.defaultRegion);
|
expect(requestParams.queries[0].region).toBe(instanceSettings.jsonData.defaultRegion);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -231,7 +231,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = {
|
const response: any = {
|
||||||
timings: [null],
|
timings: [null],
|
||||||
results: {
|
results: {
|
||||||
A: {
|
A: {
|
||||||
@@ -259,7 +259,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return series list', done => {
|
it('should return series list', done => {
|
||||||
ctx.ds.query(query).then(result => {
|
ctx.ds.query(query).then((result: any) => {
|
||||||
expect(result.data[0].target).toBe(response.results.A.series[0].name);
|
expect(result.data[0].target).toBe(response.results.A.series[0].name);
|
||||||
expect(result.data[0].datapoints[0][0]).toBe(response.results.A.series[0].points[0][0]);
|
expect(result.data[0].datapoints[0][0]).toBe(response.results.A.series[0].points[0][0]);
|
||||||
done();
|
done();
|
||||||
@@ -411,7 +411,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should generate the correct query for multilple template variables with expression', done => {
|
it('should generate the correct query for multilple template variables with expression', done => {
|
||||||
const query = {
|
const query: any = {
|
||||||
range: { from: 'now-1h', to: 'now' },
|
range: { from: 'now-1h', to: 'now' },
|
||||||
rangeRaw: { from: 1483228800, to: 1483232400 },
|
rangeRaw: { from: 1483228800, to: 1483232400 },
|
||||||
targets: [
|
targets: [
|
||||||
@@ -466,17 +466,17 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function describeMetricFindQuery(query, func) {
|
function describeMetricFindQuery(query: any, func: any) {
|
||||||
describe('metricFindQuery ' + query, () => {
|
describe('metricFindQuery ' + query, () => {
|
||||||
const scenario: any = {};
|
const scenario: any = {};
|
||||||
scenario.setup = setupCallback => {
|
scenario.setup = (setupCallback: any) => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
setupCallback();
|
setupCallback();
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(args => {
|
ctx.backendSrv.datasourceRequest = jest.fn(args => {
|
||||||
scenario.request = args.data;
|
scenario.request = args.data;
|
||||||
return Promise.resolve({ data: scenario.requestResponse });
|
return Promise.resolve({ data: scenario.requestResponse });
|
||||||
});
|
});
|
||||||
ctx.ds.metricFindQuery(query).then(args => {
|
ctx.ds.metricFindQuery(query).then((args: any) => {
|
||||||
scenario.result = args;
|
scenario.result = args;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -486,7 +486,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
describeMetricFindQuery('regions()', scenario => {
|
describeMetricFindQuery('regions()', (scenario: any) => {
|
||||||
scenario.setup(() => {
|
scenario.setup(() => {
|
||||||
scenario.requestResponse = {
|
scenario.requestResponse = {
|
||||||
results: {
|
results: {
|
||||||
@@ -504,7 +504,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describeMetricFindQuery('namespaces()', scenario => {
|
describeMetricFindQuery('namespaces()', (scenario: any) => {
|
||||||
scenario.setup(() => {
|
scenario.setup(() => {
|
||||||
scenario.requestResponse = {
|
scenario.requestResponse = {
|
||||||
results: {
|
results: {
|
||||||
@@ -522,7 +522,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describeMetricFindQuery('metrics(AWS/EC2)', scenario => {
|
describeMetricFindQuery('metrics(AWS/EC2)', (scenario: any) => {
|
||||||
scenario.setup(() => {
|
scenario.setup(() => {
|
||||||
scenario.requestResponse = {
|
scenario.requestResponse = {
|
||||||
results: {
|
results: {
|
||||||
@@ -540,7 +540,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describeMetricFindQuery('dimension_keys(AWS/EC2)', scenario => {
|
describeMetricFindQuery('dimension_keys(AWS/EC2)', (scenario: any) => {
|
||||||
scenario.setup(() => {
|
scenario.setup(() => {
|
||||||
scenario.requestResponse = {
|
scenario.requestResponse = {
|
||||||
results: {
|
results: {
|
||||||
@@ -558,7 +558,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describeMetricFindQuery('dimension_values(us-east-1,AWS/EC2,CPUUtilization,InstanceId)', scenario => {
|
describeMetricFindQuery('dimension_values(us-east-1,AWS/EC2,CPUUtilization,InstanceId)', (scenario: any) => {
|
||||||
scenario.setup(() => {
|
scenario.setup(() => {
|
||||||
scenario.requestResponse = {
|
scenario.requestResponse = {
|
||||||
results: {
|
results: {
|
||||||
@@ -576,7 +576,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describeMetricFindQuery('dimension_values(default,AWS/EC2,CPUUtilization,InstanceId)', scenario => {
|
describeMetricFindQuery('dimension_values(default,AWS/EC2,CPUUtilization,InstanceId)', (scenario: any) => {
|
||||||
scenario.setup(() => {
|
scenario.setup(() => {
|
||||||
scenario.requestResponse = {
|
scenario.requestResponse = {
|
||||||
results: {
|
results: {
|
||||||
@@ -594,7 +594,7 @@ describe('CloudWatchDatasource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describeMetricFindQuery('resource_arns(default,ec2:instance,{"environment":["production"]})', scenario => {
|
describeMetricFindQuery('resource_arns(default,ec2:instance,{"environment":["production"]})', (scenario: any) => {
|
||||||
scenario.setup(() => {
|
scenario.setup(() => {
|
||||||
scenario.requestResponse = {
|
scenario.requestResponse = {
|
||||||
results: {
|
results: {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import * as queryDef from './query_def';
|
import * as queryDef from './query_def';
|
||||||
|
import { IQService } from 'angular';
|
||||||
|
|
||||||
export class ElasticBucketAggCtrl {
|
export class ElasticBucketAggCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, uiSegmentSrv, $q, $rootScope) {
|
constructor($scope: any, uiSegmentSrv: any, $q: IQService, $rootScope: any) {
|
||||||
const bucketAggs = $scope.target.bucketAggs;
|
const bucketAggs = $scope.target.bucketAggs;
|
||||||
|
|
||||||
$scope.orderByOptions = [];
|
$scope.orderByOptions = [];
|
||||||
@@ -158,7 +159,7 @@ export class ElasticBucketAggCtrl {
|
|||||||
$scope.agg.settings.filters.push({ query: '*' });
|
$scope.agg.settings.filters.push({ query: '*' });
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.removeFiltersQuery = filter => {
|
$scope.removeFiltersQuery = (filter: any) => {
|
||||||
$scope.agg.settings.filters = _.without($scope.agg.settings.filters, filter);
|
$scope.agg.settings.filters = _.without($scope.agg.settings.filters, filter);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export class ElasticConfigCtrl {
|
|||||||
current: DataSourceInstanceSettings<ElasticsearchOptions>;
|
current: DataSourceInstanceSettings<ElasticsearchOptions>;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope) {
|
constructor($scope: any) {
|
||||||
this.current.jsonData.timeField = this.current.jsonData.timeField || '@timestamp';
|
this.current.jsonData.timeField = this.current.jsonData.timeField || '@timestamp';
|
||||||
this.current.jsonData.esVersion = this.current.jsonData.esVersion || 5;
|
this.current.jsonData.esVersion = this.current.jsonData.esVersion || 5;
|
||||||
const defaultMaxConcurrentShardRequests = this.current.jsonData.esVersion >= 70 ? 5 : 256;
|
const defaultMaxConcurrentShardRequests = this.current.jsonData.esVersion >= 70 ? 5 : 256;
|
||||||
@@ -18,7 +18,7 @@ export class ElasticConfigCtrl {
|
|||||||
this.current.jsonData.logLevelField = this.current.jsonData.logLevelField || '';
|
this.current.jsonData.logLevelField = this.current.jsonData.logLevelField || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
indexPatternTypes = [
|
indexPatternTypes: any = [
|
||||||
{ name: 'No pattern', value: undefined },
|
{ name: 'No pattern', value: undefined },
|
||||||
{ name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH' },
|
{ name: 'Hourly', value: 'Hourly', example: '[logstash-]YYYY.MM.DD.HH' },
|
||||||
{ name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD' },
|
{ name: 'Daily', value: 'Daily', example: '[logstash-]YYYY.MM.DD' },
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private request(method, url, data?) {
|
private request(method: string, url: string, data?: undefined) {
|
||||||
const options: any = {
|
const options: any = {
|
||||||
url: this.url + '/' + url,
|
url: this.url + '/' + url,
|
||||||
method: method,
|
method: method,
|
||||||
@@ -82,29 +82,29 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
return this.backendSrv.datasourceRequest(options);
|
return this.backendSrv.datasourceRequest(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private get(url) {
|
private get(url: string) {
|
||||||
const range = this.timeSrv.timeRange();
|
const range = this.timeSrv.timeRange();
|
||||||
const indexList = this.indexPattern.getIndexList(range.from.valueOf(), range.to.valueOf());
|
const indexList = this.indexPattern.getIndexList(range.from.valueOf(), range.to.valueOf());
|
||||||
if (_.isArray(indexList) && indexList.length) {
|
if (_.isArray(indexList) && indexList.length) {
|
||||||
return this.request('GET', indexList[0] + url).then(results => {
|
return this.request('GET', indexList[0] + url).then((results: any) => {
|
||||||
results.data.$$config = results.config;
|
results.data.$$config = results.config;
|
||||||
return results.data;
|
return results.data;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return this.request('GET', this.indexPattern.getIndexForToday() + url).then(results => {
|
return this.request('GET', this.indexPattern.getIndexForToday() + url).then((results: any) => {
|
||||||
results.data.$$config = results.config;
|
results.data.$$config = results.config;
|
||||||
return results.data;
|
return results.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private post(url, data) {
|
private post(url: string, data: any) {
|
||||||
return this.request('POST', url, data)
|
return this.request('POST', url, data)
|
||||||
.then(results => {
|
.then((results: any) => {
|
||||||
results.data.$$config = results.config;
|
results.data.$$config = results.config;
|
||||||
return results.data;
|
return results.data;
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err: any) => {
|
||||||
if (err.data && err.data.error) {
|
if (err.data && err.data.error) {
|
||||||
throw {
|
throw {
|
||||||
message: 'Elasticsearch error: ' + err.data.error.reason,
|
message: 'Elasticsearch error: ' + err.data.error.reason,
|
||||||
@@ -116,14 +116,14 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationQuery(options) {
|
annotationQuery(options: any) {
|
||||||
const annotation = options.annotation;
|
const annotation = options.annotation;
|
||||||
const timeField = annotation.timeField || '@timestamp';
|
const timeField = annotation.timeField || '@timestamp';
|
||||||
const queryString = annotation.query || '*';
|
const queryString = annotation.query || '*';
|
||||||
const tagsField = annotation.tagsField || 'tags';
|
const tagsField = annotation.tagsField || 'tags';
|
||||||
const textField = annotation.textField || null;
|
const textField = annotation.textField || null;
|
||||||
|
|
||||||
const range = {};
|
const range: any = {};
|
||||||
range[timeField] = {
|
range[timeField] = {
|
||||||
from: options.range.from.valueOf(),
|
from: options.range.from.valueOf(),
|
||||||
to: options.range.to.valueOf(),
|
to: options.range.to.valueOf(),
|
||||||
@@ -144,8 +144,8 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = {
|
const data: any = {
|
||||||
query: query,
|
query,
|
||||||
size: 10000,
|
size: 10000,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -168,11 +168,11 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
|
|
||||||
const payload = angular.toJson(header) + '\n' + angular.toJson(data) + '\n';
|
const payload = angular.toJson(header) + '\n' + angular.toJson(data) + '\n';
|
||||||
|
|
||||||
return this.post('_msearch', payload).then(res => {
|
return this.post('_msearch', payload).then((res: any) => {
|
||||||
const list = [];
|
const list = [];
|
||||||
const hits = res.responses[0].hits.hits;
|
const hits = res.responses[0].hits.hits;
|
||||||
|
|
||||||
const getFieldFromSource = (source, fieldName) => {
|
const getFieldFromSource = (source: any, fieldName: any) => {
|
||||||
if (!fieldName) {
|
if (!fieldName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
testDatasource() {
|
testDatasource() {
|
||||||
// validate that the index exist and has date field
|
// validate that the index exist and has date field
|
||||||
return this.getFields({ type: 'date' }).then(
|
return this.getFields({ type: 'date' }).then(
|
||||||
dateFields => {
|
(dateFields: any) => {
|
||||||
const timeField: any = _.find(dateFields, { text: this.timeField });
|
const timeField: any = _.find(dateFields, { text: this.timeField });
|
||||||
if (!timeField) {
|
if (!timeField) {
|
||||||
return {
|
return {
|
||||||
@@ -239,7 +239,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
}
|
}
|
||||||
return { status: 'success', message: 'Index OK. Time field name OK.' };
|
return { status: 'success', message: 'Index OK. Time field name OK.' };
|
||||||
},
|
},
|
||||||
err => {
|
(err: any) => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
if (err.data && err.data.error) {
|
if (err.data && err.data.error) {
|
||||||
let message = angular.toJson(err.data.error);
|
let message = angular.toJson(err.data.error);
|
||||||
@@ -254,7 +254,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getQueryHeader(searchType, timeFrom, timeTo) {
|
getQueryHeader(searchType: any, timeFrom: any, timeTo: any) {
|
||||||
const queryHeader: any = {
|
const queryHeader: any = {
|
||||||
search_type: searchType,
|
search_type: searchType,
|
||||||
ignore_unavailable: true,
|
ignore_unavailable: true,
|
||||||
@@ -319,7 +319,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
|
|
||||||
const url = this.getMultiSearchUrl();
|
const url = this.getMultiSearchUrl();
|
||||||
|
|
||||||
return this.post(url, payload).then(res => {
|
return this.post(url, payload).then((res: any) => {
|
||||||
const er = new ElasticResponse(sentTargets, res);
|
const er = new ElasticResponse(sentTargets, res);
|
||||||
if (sentTargets.some(target => target.isLogsQuery)) {
|
if (sentTargets.some(target => target.isLogsQuery)) {
|
||||||
return er.getLogs(this.logMessageField, this.logLevelField);
|
return er.getLogs(this.logMessageField, this.logLevelField);
|
||||||
@@ -329,10 +329,10 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getFields(query) {
|
getFields(query: any) {
|
||||||
const configuredEsVersion = this.esVersion;
|
const configuredEsVersion = this.esVersion;
|
||||||
return this.get('/_mapping').then(result => {
|
return this.get('/_mapping').then((result: any) => {
|
||||||
const typeMap = {
|
const typeMap: any = {
|
||||||
float: 'number',
|
float: 'number',
|
||||||
double: 'number',
|
double: 'number',
|
||||||
integer: 'number',
|
integer: 'number',
|
||||||
@@ -344,7 +344,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
nested: 'nested',
|
nested: 'nested',
|
||||||
};
|
};
|
||||||
|
|
||||||
function shouldAddField(obj, key, query) {
|
function shouldAddField(obj: any, key: any, query: any) {
|
||||||
if (key[0] === '_') {
|
if (key[0] === '_') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -358,10 +358,10 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Store subfield names: [system, process, cpu, total] -> system.process.cpu.total
|
// Store subfield names: [system, process, cpu, total] -> system.process.cpu.total
|
||||||
const fieldNameParts = [];
|
const fieldNameParts: any = [];
|
||||||
const fields = {};
|
const fields: any = {};
|
||||||
|
|
||||||
function getFieldsRecursively(obj) {
|
function getFieldsRecursively(obj: any) {
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
const subObj = obj[key];
|
const subObj = obj[key];
|
||||||
|
|
||||||
@@ -415,7 +415,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTerms(queryDef) {
|
getTerms(queryDef: any) {
|
||||||
const range = this.timeSrv.timeRange();
|
const range = this.timeSrv.timeRange();
|
||||||
const searchType = this.esVersion >= 5 ? 'query_then_fetch' : 'count';
|
const searchType = this.esVersion >= 5 ? 'query_then_fetch' : 'count';
|
||||||
const header = this.getQueryHeader(searchType, range.from, range.to);
|
const header = this.getQueryHeader(searchType, range.from, range.to);
|
||||||
@@ -427,7 +427,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
|
|
||||||
const url = this.getMultiSearchUrl();
|
const url = this.getMultiSearchUrl();
|
||||||
|
|
||||||
return this.post(url, esQuery).then(res => {
|
return this.post(url, esQuery).then((res: any) => {
|
||||||
if (!res.responses[0].aggregations) {
|
if (!res.responses[0].aggregations) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -450,7 +450,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
return '_msearch';
|
return '_msearch';
|
||||||
}
|
}
|
||||||
|
|
||||||
metricFindQuery(query) {
|
metricFindQuery(query: any) {
|
||||||
query = angular.fromJson(query);
|
query = angular.fromJson(query);
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return this.$q.when([]);
|
return this.$q.when([]);
|
||||||
@@ -472,11 +472,11 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
return this.getFields({});
|
return this.getFields({});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTagValues(options) {
|
getTagValues(options: any) {
|
||||||
return this.getTerms({ field: options.key, query: '*' });
|
return this.getTerms({ field: options.key, query: '*' });
|
||||||
}
|
}
|
||||||
|
|
||||||
targetContainsTemplate(target) {
|
targetContainsTemplate(target: any) {
|
||||||
if (this.templateSrv.variableExists(target.query) || this.templateSrv.variableExists(target.alias)) {
|
if (this.templateSrv.variableExists(target.query) || this.templateSrv.variableExists(target.alias)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -500,7 +500,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private isPrimitive(obj) {
|
private isPrimitive(obj: any) {
|
||||||
if (obj === null || obj === undefined) {
|
if (obj === null || obj === undefined) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -511,7 +511,7 @@ export class ElasticDatasource extends DataSourceApi<ElasticsearchQuery, Elastic
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private objectContainsTemplate(obj) {
|
private objectContainsTemplate(obj: any) {
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,15 @@ import * as queryDef from './query_def';
|
|||||||
import TableModel from 'app/core/table_model';
|
import TableModel from 'app/core/table_model';
|
||||||
import { DataFrame, toDataFrame, FieldType } from '@grafana/data';
|
import { DataFrame, toDataFrame, FieldType } from '@grafana/data';
|
||||||
import { DataQueryResponse } from '@grafana/ui';
|
import { DataQueryResponse } from '@grafana/ui';
|
||||||
|
import { ElasticsearchAggregation } from './types';
|
||||||
|
|
||||||
export class ElasticResponse {
|
export class ElasticResponse {
|
||||||
constructor(private targets, private response) {
|
constructor(private targets: any, private response: any) {
|
||||||
this.targets = targets;
|
this.targets = targets;
|
||||||
this.response = response;
|
this.response = response;
|
||||||
}
|
}
|
||||||
|
|
||||||
processMetrics(esAgg, target, seriesList, props) {
|
processMetrics(esAgg: any, target: any, seriesList: any, props: any) {
|
||||||
let metric, y, i, newSeries, bucket, value;
|
let metric, y, i, newSeries, bucket, value;
|
||||||
|
|
||||||
for (y = 0; y < target.metrics.length; y++) {
|
for (y = 0; y < target.metrics.length; y++) {
|
||||||
@@ -113,7 +114,7 @@ export class ElasticResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processAggregationDocs(esAgg, aggDef, target, table, props) {
|
processAggregationDocs(esAgg: any, aggDef: ElasticsearchAggregation, target: any, table: any, props: any) {
|
||||||
// add columns
|
// add columns
|
||||||
if (table.columns.length === 0) {
|
if (table.columns.length === 0) {
|
||||||
for (const propKey of _.keys(props)) {
|
for (const propKey of _.keys(props)) {
|
||||||
@@ -123,7 +124,7 @@ export class ElasticResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper func to add values to value array
|
// helper func to add values to value array
|
||||||
const addMetricValue = (values, metricName, value) => {
|
const addMetricValue = (values: any[], metricName: string, value: any) => {
|
||||||
table.addColumn({ text: metricName });
|
table.addColumn({ text: metricName });
|
||||||
values.push(value);
|
values.push(value);
|
||||||
};
|
};
|
||||||
@@ -188,8 +189,8 @@ export class ElasticResponse {
|
|||||||
|
|
||||||
// This is quite complex
|
// This is quite complex
|
||||||
// need to recurse down the nested buckets to build series
|
// need to recurse down the nested buckets to build series
|
||||||
processBuckets(aggs, target, seriesList, table, props, depth) {
|
processBuckets(aggs: any, target: any, seriesList: any, table: any, props: any, depth: any) {
|
||||||
let bucket, aggDef, esAgg, aggId;
|
let bucket, aggDef: any, esAgg, aggId;
|
||||||
const maxDepth = target.bucketAggs.length - 1;
|
const maxDepth = target.bucketAggs.length - 1;
|
||||||
|
|
||||||
for (aggId in aggs) {
|
for (aggId in aggs) {
|
||||||
@@ -224,7 +225,7 @@ export class ElasticResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getMetricName(metric) {
|
private getMetricName(metric: any) {
|
||||||
let metricDef: any = _.find(queryDef.metricAggTypes, { value: metric });
|
let metricDef: any = _.find(queryDef.metricAggTypes, { value: metric });
|
||||||
if (!metricDef) {
|
if (!metricDef) {
|
||||||
metricDef = _.find(queryDef.extendedStats, { value: metric });
|
metricDef = _.find(queryDef.extendedStats, { value: metric });
|
||||||
@@ -233,13 +234,13 @@ export class ElasticResponse {
|
|||||||
return metricDef ? metricDef.text : metric;
|
return metricDef ? metricDef.text : metric;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSeriesName(series, target, metricTypeCount) {
|
private getSeriesName(series: any, target: any, metricTypeCount: any) {
|
||||||
let metricName = this.getMetricName(series.metric);
|
let metricName = this.getMetricName(series.metric);
|
||||||
|
|
||||||
if (target.alias) {
|
if (target.alias) {
|
||||||
const regex = /\{\{([\s\S]+?)\}\}/g;
|
const regex = /\{\{([\s\S]+?)\}\}/g;
|
||||||
|
|
||||||
return target.alias.replace(regex, (match, g1, g2) => {
|
return target.alias.replace(regex, (match: any, g1: any, g2: any) => {
|
||||||
const group = g1 || g2;
|
const group = g1 || g2;
|
||||||
|
|
||||||
if (group.indexOf('term ') === 0) {
|
if (group.indexOf('term ') === 0) {
|
||||||
@@ -303,7 +304,7 @@ export class ElasticResponse {
|
|||||||
return name.trim() + ' ' + metricName;
|
return name.trim() + ' ' + metricName;
|
||||||
}
|
}
|
||||||
|
|
||||||
nameSeries(seriesList, target) {
|
nameSeries(seriesList: any, target: any) {
|
||||||
const metricTypeCount = _.uniq(_.map(seriesList, 'metric')).length;
|
const metricTypeCount = _.uniq(_.map(seriesList, 'metric')).length;
|
||||||
|
|
||||||
for (let i = 0; i < seriesList.length; i++) {
|
for (let i = 0; i < seriesList.length; i++) {
|
||||||
@@ -312,17 +313,17 @@ export class ElasticResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
processHits(hits, seriesList) {
|
processHits(hits: { total: { value: any }; hits: any[] }, seriesList: any[]) {
|
||||||
const hitsTotal = typeof hits.total === 'number' ? hits.total : hits.total.value; // <- Works with Elasticsearch 7.0+
|
const hitsTotal = typeof hits.total === 'number' ? hits.total : hits.total.value; // <- Works with Elasticsearch 7.0+
|
||||||
|
|
||||||
const series = {
|
const series: any = {
|
||||||
target: 'docs',
|
target: 'docs',
|
||||||
type: 'docs',
|
type: 'docs',
|
||||||
datapoints: [],
|
datapoints: [],
|
||||||
total: hitsTotal,
|
total: hitsTotal,
|
||||||
filterable: true,
|
filterable: true,
|
||||||
};
|
};
|
||||||
let propName, hit, doc, i;
|
let propName, hit, doc: any, i;
|
||||||
|
|
||||||
for (i = 0; i < hits.hits.length; i++) {
|
for (i = 0; i < hits.hits.length; i++) {
|
||||||
hit = hits.hits[i];
|
hit = hits.hits[i];
|
||||||
@@ -347,7 +348,7 @@ export class ElasticResponse {
|
|||||||
seriesList.push(series);
|
seriesList.push(series);
|
||||||
}
|
}
|
||||||
|
|
||||||
trimDatapoints(aggregations, target) {
|
trimDatapoints(aggregations: any, target: any) {
|
||||||
const histogram: any = _.find(target.bucketAggs, { type: 'date_histogram' });
|
const histogram: any = _.find(target.bucketAggs, { type: 'date_histogram' });
|
||||||
|
|
||||||
const shouldDropFirstAndLast = histogram && histogram.settings && histogram.settings.trimEdges;
|
const shouldDropFirstAndLast = histogram && histogram.settings && histogram.settings.trimEdges;
|
||||||
@@ -362,7 +363,7 @@ export class ElasticResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getErrorFromElasticResponse(response, err) {
|
getErrorFromElasticResponse(response: any, err: any) {
|
||||||
const result: any = {};
|
const result: any = {};
|
||||||
result.data = JSON.stringify(err, null, 4);
|
result.data = JSON.stringify(err, null, 4);
|
||||||
if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) {
|
if (err.root_cause && err.root_cause.length > 0 && err.root_cause[0].reason) {
|
||||||
@@ -394,7 +395,7 @@ export class ElasticResponse {
|
|||||||
if (response.aggregations) {
|
if (response.aggregations) {
|
||||||
const aggregations = response.aggregations;
|
const aggregations = response.aggregations;
|
||||||
const target = this.targets[i];
|
const target = this.targets[i];
|
||||||
const tmpSeriesList = [];
|
const tmpSeriesList: any[] = [];
|
||||||
const table = new TableModel();
|
const table = new TableModel();
|
||||||
|
|
||||||
this.processBuckets(aggregations, target, tmpSeriesList, table, {}, 0);
|
this.processBuckets(aggregations, target, tmpSeriesList, table, {}, 0);
|
||||||
@@ -426,7 +427,7 @@ export class ElasticResponse {
|
|||||||
|
|
||||||
const hits = response.hits;
|
const hits = response.hits;
|
||||||
let propNames: string[] = [];
|
let propNames: string[] = [];
|
||||||
let propName, hit, doc, i;
|
let propName, hit, doc: any, i;
|
||||||
|
|
||||||
for (i = 0; i < hits.hits.length; i++) {
|
for (i = 0; i < hits.hits.length; i++) {
|
||||||
hit = hits.hits[i];
|
hit = hits.hits[i];
|
||||||
@@ -534,7 +535,7 @@ export class ElasticResponse {
|
|||||||
if (response.aggregations) {
|
if (response.aggregations) {
|
||||||
const aggregations = response.aggregations;
|
const aggregations = response.aggregations;
|
||||||
const target = this.targets[n];
|
const target = this.targets[n];
|
||||||
const tmpSeriesList = [];
|
const tmpSeriesList: any[] = [];
|
||||||
const table = new TableModel();
|
const table = new TableModel();
|
||||||
|
|
||||||
this.processBuckets(aggregations, target, tmpSeriesList, table, {}, 0);
|
this.processBuckets(aggregations, target, tmpSeriesList, table, {}, 0);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { toUtc, dateTime } from '@grafana/data';
|
import { toUtc, dateTime } from '@grafana/data';
|
||||||
|
|
||||||
const intervalMap = {
|
const intervalMap: any = {
|
||||||
Hourly: { startOf: 'hour', amount: 'hours' },
|
Hourly: { startOf: 'hour', amount: 'hours' },
|
||||||
Daily: { startOf: 'day', amount: 'days' },
|
Daily: { startOf: 'day', amount: 'days' },
|
||||||
Weekly: { startOf: 'isoWeek', amount: 'weeks' },
|
Weekly: { startOf: 'isoWeek', amount: 'weeks' },
|
||||||
@@ -9,7 +9,7 @@ const intervalMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class IndexPattern {
|
export class IndexPattern {
|
||||||
constructor(private pattern, private interval: string | null) {}
|
constructor(private pattern: any, private interval: string | null) {}
|
||||||
|
|
||||||
getIndexForToday() {
|
getIndexForToday() {
|
||||||
if (this.interval) {
|
if (this.interval) {
|
||||||
@@ -19,7 +19,7 @@ export class IndexPattern {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getIndexList(from, to) {
|
getIndexList(from: any, to: any) {
|
||||||
if (!this.interval) {
|
if (!this.interval) {
|
||||||
return this.pattern;
|
return this.pattern;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import coreModule from 'app/core/core_module';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import * as queryDef from './query_def';
|
import * as queryDef from './query_def';
|
||||||
import { ElasticsearchAggregation } from './types';
|
import { ElasticsearchAggregation } from './types';
|
||||||
|
import { IQService } from 'angular';
|
||||||
|
|
||||||
export class ElasticMetricAggCtrl {
|
export class ElasticMetricAggCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, uiSegmentSrv, $q, $rootScope) {
|
constructor($scope: any, uiSegmentSrv: any, $q: IQService, $rootScope: any) {
|
||||||
const metricAggs: ElasticsearchAggregation[] = $scope.target.metrics;
|
const metricAggs: ElasticsearchAggregation[] = $scope.target.metrics;
|
||||||
$scope.metricAggTypes = queryDef.getMetricAggTypes($scope.esVersion);
|
$scope.metricAggTypes = queryDef.getMetricAggTypes($scope.esVersion);
|
||||||
$scope.extendedStats = queryDef.extendedStats;
|
$scope.extendedStats = queryDef.extendedStats;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function elasticPipelineVariables() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const newVariable = index => {
|
const newVariable = (index: any) => {
|
||||||
return {
|
return {
|
||||||
name: 'var' + index,
|
name: 'var' + index,
|
||||||
pipelineAgg: 'select metric',
|
pipelineAgg: 'select metric',
|
||||||
@@ -23,7 +23,7 @@ const newVariable = index => {
|
|||||||
|
|
||||||
export class ElasticPipelineVariablesCtrl {
|
export class ElasticPipelineVariablesCtrl {
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope) {
|
constructor($scope: any) {
|
||||||
$scope.variables = $scope.variables || [newVariable(1)];
|
$scope.variables = $scope.variables || [newVariable(1)];
|
||||||
|
|
||||||
$scope.onChangeInternal = () => {
|
$scope.onChangeInternal = () => {
|
||||||
@@ -35,7 +35,7 @@ export class ElasticPipelineVariablesCtrl {
|
|||||||
$scope.onChange();
|
$scope.onChange();
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.remove = index => {
|
$scope.remove = (index: number) => {
|
||||||
$scope.variables.splice(index, 1);
|
$scope.variables.splice(index, 1);
|
||||||
$scope.onChange();
|
$scope.onChange();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import * as queryDef from './query_def';
|
import * as queryDef from './query_def';
|
||||||
|
import { ElasticsearchAggregation } from './types';
|
||||||
|
|
||||||
export class ElasticQueryBuilder {
|
export class ElasticQueryBuilder {
|
||||||
timeField: string;
|
timeField: string;
|
||||||
esVersion: number;
|
esVersion: number;
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options: any) {
|
||||||
this.timeField = options.timeField;
|
this.timeField = options.timeField;
|
||||||
this.esVersion = options.esVersion;
|
this.esVersion = options.esVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRangeFilter() {
|
getRangeFilter() {
|
||||||
const filter = {};
|
const filter: any = {};
|
||||||
filter[this.timeField] = {
|
filter[this.timeField] = {
|
||||||
gte: '$timeFrom',
|
gte: '$timeFrom',
|
||||||
lte: '$timeTo',
|
lte: '$timeTo',
|
||||||
@@ -20,7 +21,7 @@ export class ElasticQueryBuilder {
|
|||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTermsAgg(aggDef, queryNode, target) {
|
buildTermsAgg(aggDef: ElasticsearchAggregation, queryNode: { terms?: any; aggs?: any }, target: { metrics: any[] }) {
|
||||||
let metricRef, metric, y;
|
let metricRef, metric, y;
|
||||||
queryNode.terms = { field: aggDef.field };
|
queryNode.terms = { field: aggDef.field };
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ export class ElasticQueryBuilder {
|
|||||||
return queryNode;
|
return queryNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDateHistogramAgg(aggDef) {
|
getDateHistogramAgg(aggDef: ElasticsearchAggregation) {
|
||||||
const esAgg: any = {};
|
const esAgg: any = {};
|
||||||
const settings = aggDef.settings || {};
|
const settings = aggDef.settings || {};
|
||||||
esAgg.interval = settings.interval;
|
esAgg.interval = settings.interval;
|
||||||
@@ -87,7 +88,7 @@ export class ElasticQueryBuilder {
|
|||||||
return esAgg;
|
return esAgg;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHistogramAgg(aggDef) {
|
getHistogramAgg(aggDef: ElasticsearchAggregation) {
|
||||||
const esAgg: any = {};
|
const esAgg: any = {};
|
||||||
const settings = aggDef.settings || {};
|
const settings = aggDef.settings || {};
|
||||||
esAgg.interval = settings.interval;
|
esAgg.interval = settings.interval;
|
||||||
@@ -100,8 +101,8 @@ export class ElasticQueryBuilder {
|
|||||||
return esAgg;
|
return esAgg;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFiltersAgg(aggDef) {
|
getFiltersAgg(aggDef: ElasticsearchAggregation) {
|
||||||
const filterObj = {};
|
const filterObj: any = {};
|
||||||
for (let i = 0; i < aggDef.settings.filters.length; i++) {
|
for (let i = 0; i < aggDef.settings.filters.length; i++) {
|
||||||
const query = aggDef.settings.filters[i].query;
|
const query = aggDef.settings.filters[i].query;
|
||||||
let label = aggDef.settings.filters[i].label;
|
let label = aggDef.settings.filters[i].label;
|
||||||
@@ -117,7 +118,7 @@ export class ElasticQueryBuilder {
|
|||||||
return filterObj;
|
return filterObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
documentQuery(query, size) {
|
documentQuery(query: any, size: number) {
|
||||||
query.size = size;
|
query.size = size;
|
||||||
query.sort = {};
|
query.sort = {};
|
||||||
query.sort[this.timeField] = { order: 'desc', unmapped_type: 'boolean' };
|
query.sort[this.timeField] = { order: 'desc', unmapped_type: 'boolean' };
|
||||||
@@ -136,12 +137,12 @@ export class ElasticQueryBuilder {
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
addAdhocFilters(query, adhocFilters) {
|
addAdhocFilters(query: any, adhocFilters: any) {
|
||||||
if (!adhocFilters) {
|
if (!adhocFilters) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let i, filter, condition, queryCondition;
|
let i, filter, condition: any, queryCondition: any;
|
||||||
|
|
||||||
for (i = 0; i < adhocFilters.length; i++) {
|
for (i = 0; i < adhocFilters.length; i++) {
|
||||||
filter = adhocFilters[i];
|
filter = adhocFilters[i];
|
||||||
@@ -183,7 +184,7 @@ export class ElasticQueryBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
build(target, adhocFilters?, queryString?) {
|
build(target: any, adhocFilters?: any, queryString?: string) {
|
||||||
// make sure query has defaults;
|
// make sure query has defaults;
|
||||||
target.metrics = target.metrics || [queryDef.defaultMetricAgg()];
|
target.metrics = target.metrics || [queryDef.defaultMetricAgg()];
|
||||||
target.bucketAggs = target.bucketAggs || [queryDef.defaultBucketAgg()];
|
target.bucketAggs = target.bucketAggs || [queryDef.defaultBucketAgg()];
|
||||||
@@ -224,7 +225,7 @@ export class ElasticQueryBuilder {
|
|||||||
|
|
||||||
for (i = 0; i < target.bucketAggs.length; i++) {
|
for (i = 0; i < target.bucketAggs.length; i++) {
|
||||||
const aggDef = target.bucketAggs[i];
|
const aggDef = target.bucketAggs[i];
|
||||||
const esAgg = {};
|
const esAgg: any = {};
|
||||||
|
|
||||||
switch (aggDef.type) {
|
switch (aggDef.type) {
|
||||||
case 'date_histogram': {
|
case 'date_histogram': {
|
||||||
@@ -265,8 +266,8 @@ export class ElasticQueryBuilder {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const aggField = {};
|
const aggField: any = {};
|
||||||
let metricAgg = null;
|
let metricAgg: any = null;
|
||||||
|
|
||||||
if (queryDef.isPipelineAgg(metric.type)) {
|
if (queryDef.isPipelineAgg(metric.type)) {
|
||||||
if (queryDef.isPipelineAggWithMultipleBucketPaths(metric.type)) {
|
if (queryDef.isPipelineAggWithMultipleBucketPaths(metric.type)) {
|
||||||
@@ -323,7 +324,7 @@ export class ElasticQueryBuilder {
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
getTermsQuery(queryDef) {
|
getTermsQuery(queryDef: any) {
|
||||||
const query: any = {
|
const query: any = {
|
||||||
size: 0,
|
size: 0,
|
||||||
query: {
|
query: {
|
||||||
@@ -368,7 +369,7 @@ export class ElasticQueryBuilder {
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
getLogsQuery(target, querystring) {
|
getLogsQuery(target: any, querystring: string) {
|
||||||
let query: any = {
|
let query: any = {
|
||||||
size: 0,
|
size: 0,
|
||||||
query: {
|
query: {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import './bucket_agg';
|
|||||||
import './metric_agg';
|
import './metric_agg';
|
||||||
import './pipeline_variables';
|
import './pipeline_variables';
|
||||||
|
|
||||||
import angular from 'angular';
|
import angular, { auto } from 'angular';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import * as queryDef from './query_def';
|
import * as queryDef from './query_def';
|
||||||
import { QueryCtrl } from 'app/plugins/sdk';
|
import { QueryCtrl } from 'app/plugins/sdk';
|
||||||
@@ -15,7 +15,7 @@ export class ElasticQueryCtrl extends QueryCtrl {
|
|||||||
rawQueryOld: string;
|
rawQueryOld: string;
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, $injector, private $rootScope, private uiSegmentSrv) {
|
constructor($scope: any, $injector: auto.IInjectorService, private $rootScope: any, private uiSegmentSrv: any) {
|
||||||
super($scope, $injector);
|
super($scope, $injector);
|
||||||
|
|
||||||
this.esVersion = this.datasource.esVersion;
|
this.esVersion = this.datasource.esVersion;
|
||||||
@@ -35,7 +35,7 @@ export class ElasticQueryCtrl extends QueryCtrl {
|
|||||||
this.queryUpdated();
|
this.queryUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
getFields(type) {
|
getFields(type: any) {
|
||||||
const jsonStr = angular.toJson({ find: 'fields', type: type });
|
const jsonStr = angular.toJson({ find: 'fields', type: type });
|
||||||
return this.datasource
|
return this.datasource
|
||||||
.metricFindQuery(jsonStr)
|
.metricFindQuery(jsonStr)
|
||||||
@@ -98,7 +98,7 @@ export class ElasticQueryCtrl extends QueryCtrl {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQueryError(err) {
|
handleQueryError(err: any): any[] {
|
||||||
this.error = err.message || 'Failed to issue metric query';
|
this.error = err.message || 'Failed to issue metric query';
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export const movingAvgModelOptions = [
|
|||||||
{ text: 'Holt Winters', value: 'holt_winters' },
|
{ text: 'Holt Winters', value: 'holt_winters' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const pipelineOptions = {
|
export const pipelineOptions: any = {
|
||||||
moving_avg: [
|
moving_avg: [
|
||||||
{ text: 'window', default: 5 },
|
{ text: 'window', default: 5 },
|
||||||
{ text: 'model', default: 'simple' },
|
{ text: 'model', default: 'simple' },
|
||||||
@@ -139,7 +139,7 @@ export const pipelineOptions = {
|
|||||||
bucket_script: [],
|
bucket_script: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const movingAvgModelSettings = {
|
export const movingAvgModelSettings: any = {
|
||||||
simple: [],
|
simple: [],
|
||||||
linear: [],
|
linear: [],
|
||||||
ewma: [{ text: 'Alpha', value: 'alpha', default: undefined }],
|
ewma: [{ text: 'Alpha', value: 'alpha', default: undefined }],
|
||||||
@@ -153,7 +153,7 @@ export const movingAvgModelSettings = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getMetricAggTypes(esVersion) {
|
export function getMetricAggTypes(esVersion: any) {
|
||||||
return _.filter(metricAggTypes, f => {
|
return _.filter(metricAggTypes, f => {
|
||||||
if (f.minVersion) {
|
if (f.minVersion) {
|
||||||
return f.minVersion <= esVersion;
|
return f.minVersion <= esVersion;
|
||||||
@@ -163,7 +163,7 @@ export function getMetricAggTypes(esVersion) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPipelineOptions(metric) {
|
export function getPipelineOptions(metric: any) {
|
||||||
if (!isPipelineAgg(metric.type)) {
|
if (!isPipelineAgg(metric.type)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ export function getPipelineOptions(metric) {
|
|||||||
return pipelineOptions[metric.type];
|
return pipelineOptions[metric.type];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPipelineAgg(metricType) {
|
export function isPipelineAgg(metricType: any) {
|
||||||
if (metricType) {
|
if (metricType) {
|
||||||
const po = pipelineOptions[metricType];
|
const po = pipelineOptions[metricType];
|
||||||
return po !== null && po !== undefined;
|
return po !== null && po !== undefined;
|
||||||
@@ -180,7 +180,7 @@ export function isPipelineAgg(metricType) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPipelineAggWithMultipleBucketPaths(metricType) {
|
export function isPipelineAggWithMultipleBucketPaths(metricType: any) {
|
||||||
if (metricType) {
|
if (metricType) {
|
||||||
return metricAggTypes.find(t => t.value === metricType && t.supportsMultipleBucketPaths) !== undefined;
|
return metricAggTypes.find(t => t.value === metricType && t.supportsMultipleBucketPaths) !== undefined;
|
||||||
}
|
}
|
||||||
@@ -188,8 +188,8 @@ export function isPipelineAggWithMultipleBucketPaths(metricType) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getPipelineAggOptions(targets) {
|
export function getPipelineAggOptions(targets: any) {
|
||||||
const result = [];
|
const result: any[] = [];
|
||||||
_.each(targets.metrics, metric => {
|
_.each(targets.metrics, metric => {
|
||||||
if (!isPipelineAgg(metric.type)) {
|
if (!isPipelineAgg(metric.type)) {
|
||||||
result.push({ text: describeMetric(metric), value: metric.id });
|
result.push({ text: describeMetric(metric), value: metric.id });
|
||||||
@@ -199,8 +199,8 @@ export function getPipelineAggOptions(targets) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMovingAvgSettings(model, filtered) {
|
export function getMovingAvgSettings(model: any, filtered: boolean) {
|
||||||
const filteredResult = [];
|
const filteredResult: any[] = [];
|
||||||
if (filtered) {
|
if (filtered) {
|
||||||
_.each(movingAvgModelSettings[model], setting => {
|
_.each(movingAvgModelSettings[model], setting => {
|
||||||
if (!setting.isCheckbox) {
|
if (!setting.isCheckbox) {
|
||||||
@@ -212,8 +212,8 @@ export function getMovingAvgSettings(model, filtered) {
|
|||||||
return movingAvgModelSettings[model];
|
return movingAvgModelSettings[model];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getOrderByOptions(target) {
|
export function getOrderByOptions(target: any) {
|
||||||
const metricRefs = [];
|
const metricRefs: any[] = [];
|
||||||
_.each(target.metrics, metric => {
|
_.each(target.metrics, metric => {
|
||||||
if (metric.type !== 'count') {
|
if (metric.type !== 'count') {
|
||||||
metricRefs.push({ text: describeMetric(metric), value: metric.id });
|
metricRefs.push({ text: describeMetric(metric), value: metric.id });
|
||||||
@@ -223,12 +223,12 @@ export function getOrderByOptions(target) {
|
|||||||
return orderByOptions.concat(metricRefs);
|
return orderByOptions.concat(metricRefs);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function describeOrder(order) {
|
export function describeOrder(order: string) {
|
||||||
const def: any = _.find(orderOptions, { value: order });
|
const def: any = _.find(orderOptions, { value: order });
|
||||||
return def.text;
|
return def.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function describeMetric(metric) {
|
export function describeMetric(metric: { type: string; field: string }) {
|
||||||
const def: any = _.find(metricAggTypes, { value: metric.type });
|
const def: any = _.find(metricAggTypes, { value: metric.type });
|
||||||
if (!def.requiresField && !isPipelineAgg(metric.type)) {
|
if (!def.requiresField && !isPipelineAgg(metric.type)) {
|
||||||
return def.text;
|
return def.text;
|
||||||
@@ -236,7 +236,7 @@ export function describeMetric(metric) {
|
|||||||
return def.text + ' ' + metric.field;
|
return def.text + ' ' + metric.field;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function describeOrderBy(orderBy, target) {
|
export function describeOrderBy(orderBy: any, target: any) {
|
||||||
const def: any = _.find(orderByOptions, { value: orderBy });
|
const def: any = _.find(orderByOptions, { value: orderBy });
|
||||||
if (def) {
|
if (def) {
|
||||||
return def.text;
|
return def.text;
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should translate index pattern to current day', () => {
|
it('should translate index pattern to current day', () => {
|
||||||
let requestOptions;
|
let requestOptions: any;
|
||||||
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
ctx.backendSrv.datasourceRequest = jest.fn(options => {
|
||||||
requestOptions = options;
|
requestOptions = options;
|
||||||
return Promise.resolve({ data: {} });
|
return Promise.resolve({ data: {} });
|
||||||
@@ -83,7 +83,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('When issuing metric query with interval pattern', () => {
|
describe('When issuing metric query with interval pattern', () => {
|
||||||
let requestOptions, parts, header, query, result;
|
let requestOptions: any, parts: any, header: any, query: any, result: any;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
@@ -154,7 +154,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('When issuing logs query with interval pattern', () => {
|
describe('When issuing logs query with interval pattern', () => {
|
||||||
let query, queryBuilderSpy;
|
let query, queryBuilderSpy: any;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
@@ -249,7 +249,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('When issuing document query', () => {
|
describe('When issuing document query', () => {
|
||||||
let requestOptions, parts, header;
|
let requestOptions: any, parts: any, header: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
@@ -539,7 +539,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('When issuing aggregation query on es5.x', () => {
|
describe('When issuing aggregation query on es5.x', () => {
|
||||||
let requestOptions, parts, header;
|
let requestOptions: any, parts: any, header: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
@@ -582,7 +582,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('When issuing metricFind query on es5.x', () => {
|
describe('When issuing metricFind query on es5.x', () => {
|
||||||
let requestOptions, parts, header, body, results;
|
let requestOptions: any, parts, header: any, body: any, results: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
createDatasource({
|
createDatasource({
|
||||||
@@ -615,7 +615,7 @@ describe('ElasticDatasource', function(this: any) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.ds.metricFindQuery('{"find": "terms", "field": "test"}').then(res => {
|
ctx.ds.metricFindQuery('{"find": "terms", "field": "test"}').then((res: any) => {
|
||||||
results = res;
|
results = res;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { ElasticResponse } from '../elastic_response';
|
|||||||
|
|
||||||
describe('ElasticResponse', () => {
|
describe('ElasticResponse', () => {
|
||||||
let targets;
|
let targets;
|
||||||
let response;
|
let response: any;
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
describe('simple query and count', () => {
|
describe('simple query and count', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -48,7 +48,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('simple query count & avg aggregation', () => {
|
describe('simple query count & avg aggregation', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -97,7 +97,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('single group by query one metric', () => {
|
describe('single group by query one metric', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -149,7 +149,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('single group by query two metrics', () => {
|
describe('single group by query two metrics', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -209,7 +209,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with percentiles ', () => {
|
describe('with percentiles ', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -257,7 +257,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with extended_stats', () => {
|
describe('with extended_stats', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -333,7 +333,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('single group by with alias pattern', () => {
|
describe('single group by with alias pattern', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -394,7 +394,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('histogram response', () => {
|
describe('histogram response', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -426,7 +426,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with two filters agg', () => {
|
describe('with two filters agg', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -583,7 +583,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('No group by time with percentiles ', () => {
|
describe('No group by time with percentiles ', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -720,7 +720,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('with bucket_script ', () => {
|
describe('with bucket_script ', () => {
|
||||||
let result;
|
let result: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
targets = [
|
targets = [
|
||||||
@@ -861,7 +861,7 @@ describe('ElasticResponse', () => {
|
|||||||
expect(result.data[0].fields).toContainEqual({ name: '@timestamp', type: 'time' });
|
expect(result.data[0].fields).toContainEqual({ name: '@timestamp', type: 'time' });
|
||||||
expect(result.data[0].fields).toContainEqual({ name: 'host', type: 'string' });
|
expect(result.data[0].fields).toContainEqual({ name: 'host', type: 'string' });
|
||||||
expect(result.data[0].fields).toContainEqual({ name: 'message', type: 'string' });
|
expect(result.data[0].fields).toContainEqual({ name: 'message', type: 'string' });
|
||||||
result.data[0].rows.forEach((row, i) => {
|
result.data[0].rows.forEach((row: any, i: number) => {
|
||||||
expect(row).toContain(response.responses[0].hits.hits[i]._id);
|
expect(row).toContain(response.responses[0].hits.hits[i]._id);
|
||||||
expect(row).toContain(response.responses[0].hits.hits[i]._type);
|
expect(row).toContain(response.responses[0].hits.hits[i]._type);
|
||||||
expect(row).toContain(response.responses[0].hits.hits[i]._index);
|
expect(row).toContain(response.responses[0].hits.hits[i]._index);
|
||||||
@@ -869,7 +869,7 @@ describe('ElasticResponse', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(result.data[1]).toHaveProperty('name', 'Count');
|
expect(result.data[1]).toHaveProperty('name', 'Count');
|
||||||
response.responses[0].aggregations['2'].buckets.forEach(bucket => {
|
response.responses[0].aggregations['2'].buckets.forEach((bucket: any) => {
|
||||||
expect(result.data[1].rows).toContainEqual([bucket.doc_count, bucket.key]);
|
expect(result.data[1].rows).toContainEqual([bucket.doc_count, bucket.key]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ElasticQueryBuilder } from '../query_builder';
|
import { ElasticQueryBuilder } from '../query_builder';
|
||||||
|
|
||||||
describe('ElasticQueryBuilder', () => {
|
describe('ElasticQueryBuilder', () => {
|
||||||
let builder;
|
let builder: any;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
builder = new ElasticQueryBuilder({ timeField: '@timestamp' });
|
builder = new ElasticQueryBuilder({ timeField: '@timestamp' });
|
||||||
@@ -103,6 +103,7 @@ describe('ElasticQueryBuilder', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
100,
|
100,
|
||||||
|
// @ts-ignore
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1,3 +1,5 @@
|
|||||||
|
import { auto } from 'angular';
|
||||||
|
|
||||||
export class QueryCtrl {
|
export class QueryCtrl {
|
||||||
target: any;
|
target: any;
|
||||||
datasource: any;
|
datasource: any;
|
||||||
@@ -6,7 +8,7 @@ export class QueryCtrl {
|
|||||||
hasRawMode: boolean;
|
hasRawMode: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
|
|
||||||
constructor(public $scope, _$injector) {
|
constructor(public $scope: any, _$injector: auto.IInjectorService) {
|
||||||
this.panelCtrl = this.panelCtrl || { panel: {} };
|
this.panelCtrl = this.panelCtrl || { panel: {} };
|
||||||
this.target = this.target || { target: '' };
|
this.target = this.target || { target: '' };
|
||||||
this.panel = this.panelCtrl.panel;
|
this.panel = this.panelCtrl.panel;
|
||||||
|
|||||||
+6
-4
@@ -1,3 +1,5 @@
|
|||||||
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
|
||||||
export class AzureMonitorAnnotationsQueryCtrl {
|
export class AzureMonitorAnnotationsQueryCtrl {
|
||||||
static templateUrl = 'partials/annotations.editor.html';
|
static templateUrl = 'partials/annotations.editor.html';
|
||||||
datasource: any;
|
datasource: any;
|
||||||
@@ -9,7 +11,7 @@ export class AzureMonitorAnnotationsQueryCtrl {
|
|||||||
'<your table>\n| where $__timeFilter() \n| project TimeGenerated, Text=YourTitleColumn, Tags="tag1,tag2"';
|
'<your table>\n| where $__timeFilter() \n| project TimeGenerated, Text=YourTitleColumn, Tags="tag1,tag2"';
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(private templateSrv) {
|
constructor(private templateSrv: TemplateSrv) {
|
||||||
this.annotation.queryType = this.annotation.queryType || 'Azure Log Analytics';
|
this.annotation.queryType = this.annotation.queryType || 'Azure Log Analytics';
|
||||||
this.annotation.rawQuery = this.annotation.rawQuery || this.defaultQuery;
|
this.annotation.rawQuery = this.annotation.rawQuery || this.defaultQuery;
|
||||||
this.initDropdowns();
|
this.initDropdowns();
|
||||||
@@ -25,7 +27,7 @@ export class AzureMonitorAnnotationsQueryCtrl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.datasource.azureMonitorDatasource.getSubscriptions().then(subs => {
|
return this.datasource.azureMonitorDatasource.getSubscriptions().then((subs: any[]) => {
|
||||||
this.subscriptions = subs;
|
this.subscriptions = subs;
|
||||||
|
|
||||||
if (!this.annotation.subscription && this.annotation.queryType === 'Azure Log Analytics') {
|
if (!this.annotation.subscription && this.annotation.queryType === 'Azure Log Analytics') {
|
||||||
@@ -45,7 +47,7 @@ export class AzureMonitorAnnotationsQueryCtrl {
|
|||||||
|
|
||||||
return this.datasource
|
return this.datasource
|
||||||
.getAzureLogAnalyticsWorkspaces(this.annotation.subscription)
|
.getAzureLogAnalyticsWorkspaces(this.annotation.subscription)
|
||||||
.then(list => {
|
.then((list: any[]) => {
|
||||||
this.workspaces = list;
|
this.workspaces = list;
|
||||||
if (list.length > 0 && !this.annotation.workspace) {
|
if (list.length > 0 && !this.annotation.workspace) {
|
||||||
this.annotation.workspace = list[0].value;
|
this.annotation.workspace = list[0].value;
|
||||||
@@ -72,6 +74,6 @@ export class AzureMonitorAnnotationsQueryCtrl {
|
|||||||
};
|
};
|
||||||
|
|
||||||
get templateVariables() {
|
get templateVariables() {
|
||||||
return this.templateSrv.variables.map(t => '$' + t.name);
|
return this.templateSrv.variables.map((t: any) => '$' + t.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-11
@@ -1,4 +1,5 @@
|
|||||||
import AzureMonitorDatasource from '../datasource';
|
import AzureMonitorDatasource from '../datasource';
|
||||||
|
// @ts-ignore
|
||||||
import Q from 'q';
|
import Q from 'q';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
import { toUtc } from '@grafana/data';
|
import { toUtc } from '@grafana/data';
|
||||||
@@ -46,7 +47,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return success status', () => {
|
it('should return success status', () => {
|
||||||
return ctx.ds.testDatasource().then(results => {
|
return ctx.ds.testDatasource().then((results: any) => {
|
||||||
expect(results.status).toEqual('success');
|
expect(results.status).toEqual('success');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -71,7 +72,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return error status and a detailed error message', () => {
|
it('should return error status and a detailed error message', () => {
|
||||||
return ctx.ds.testDatasource().then(results => {
|
return ctx.ds.testDatasource().then((results: any) => {
|
||||||
expect(results.status).toEqual('error');
|
expect(results.status).toEqual('error');
|
||||||
expect(results.message).toEqual(
|
expect(results.message).toEqual(
|
||||||
'1. Application Insights: Not Found: Invalid Application Id for Application Insights service. '
|
'1. Application Insights: Not Found: Invalid Application Id for Application Insights service. '
|
||||||
@@ -99,7 +100,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return error status and a detailed error message', () => {
|
it('should return error status and a detailed error message', () => {
|
||||||
return ctx.ds.testDatasource().then(results => {
|
return ctx.ds.testDatasource().then((results: any) => {
|
||||||
expect(results.status).toEqual('error');
|
expect(results.status).toEqual('error');
|
||||||
expect(results.message).toEqual('1. Application Insights: Error: SomeOtherError. An error message. ');
|
expect(results.message).toEqual('1. Application Insights: Error: SomeOtherError. An error message. ');
|
||||||
});
|
});
|
||||||
@@ -149,7 +150,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return a single datapoint', () => {
|
it('should return a single datapoint', () => {
|
||||||
return ctx.ds.query(options).then(results => {
|
return ctx.ds.query(options).then((results: any) => {
|
||||||
expect(results.data.length).toBe(1);
|
expect(results.data.length).toBe(1);
|
||||||
expect(results.data[0].datapoints.length).toBe(1);
|
expect(results.data[0].datapoints.length).toBe(1);
|
||||||
expect(results.data[0].target).toEqual('exceptions/server');
|
expect(results.data[0].target).toEqual('exceptions/server');
|
||||||
@@ -196,7 +197,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
it('should return a list of datapoints', () => {
|
||||||
return ctx.ds.query(options).then(results => {
|
return ctx.ds.query(options).then((results: any) => {
|
||||||
expect(results.data.length).toBe(1);
|
expect(results.data.length).toBe(1);
|
||||||
expect(results.data[0].datapoints.length).toBe(2);
|
expect(results.data[0].datapoints.length).toBe(2);
|
||||||
expect(results.data[0].target).toEqual('exceptions/server');
|
expect(results.data[0].target).toEqual('exceptions/server');
|
||||||
@@ -267,7 +268,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
it('should return a list of datapoints', () => {
|
||||||
return ctx.ds.query(options).then(results => {
|
return ctx.ds.query(options).then((results: any) => {
|
||||||
expect(results.data.length).toBe(3);
|
expect(results.data.length).toBe(3);
|
||||||
expect(results.data[0].datapoints.length).toBe(2);
|
expect(results.data[0].datapoints.length).toBe(2);
|
||||||
expect(results.data[0].target).toEqual('exceptions/server{client/city="Miami"}');
|
expect(results.data[0].target).toEqual('exceptions/server{client/city="Miami"}');
|
||||||
@@ -292,7 +293,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of datapoints', () => {
|
it('should return a list of datapoints', () => {
|
||||||
return ctx.ds.query(options).then(results => {
|
return ctx.ds.query(options).then((results: any) => {
|
||||||
expect(results.data.length).toBe(3);
|
expect(results.data.length).toBe(3);
|
||||||
expect(results.data[0].datapoints.length).toBe(2);
|
expect(results.data[0].datapoints.length).toBe(2);
|
||||||
expect(results.data[0].target).toEqual('exceptions/server + client/city + Miami');
|
expect(results.data[0].target).toEqual('exceptions/server + client/city + Miami');
|
||||||
@@ -323,7 +324,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of metric names', () => {
|
it('should return a list of metric names', () => {
|
||||||
return ctx.ds.metricFindQuery('appInsightsMetricNames()').then(results => {
|
return ctx.ds.metricFindQuery('appInsightsMetricNames()').then((results: any) => {
|
||||||
expect(results.length).toBe(2);
|
expect(results.length).toBe(2);
|
||||||
expect(results[0].text).toBe('exceptions/server');
|
expect(results[0].text).toBe('exceptions/server');
|
||||||
expect(results[0].value).toBe('exceptions/server');
|
expect(results[0].value).toBe('exceptions/server');
|
||||||
@@ -361,7 +362,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of group bys', () => {
|
it('should return a list of group bys', () => {
|
||||||
return ctx.ds.metricFindQuery('appInsightsGroupBys(requests/count)').then(results => {
|
return ctx.ds.metricFindQuery('appInsightsGroupBys(requests/count)').then((results: any) => {
|
||||||
expect(results[0].text).toContain('client/os');
|
expect(results[0].text).toContain('client/os');
|
||||||
expect(results[0].value).toContain('client/os');
|
expect(results[0].value).toContain('client/os');
|
||||||
expect(results[1].text).toContain('client/city');
|
expect(results[1].text).toContain('client/city');
|
||||||
@@ -389,7 +390,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of metric names', () => {
|
it('should return a list of metric names', () => {
|
||||||
return ctx.ds.getAppInsightsMetricNames().then(results => {
|
return ctx.ds.getAppInsightsMetricNames().then((results: any) => {
|
||||||
expect(results.length).toBe(2);
|
expect(results.length).toBe(2);
|
||||||
expect(results[0].text).toBe('exceptions/server');
|
expect(results[0].text).toBe('exceptions/server');
|
||||||
expect(results[0].value).toBe('exceptions/server');
|
expect(results[0].value).toBe('exceptions/server');
|
||||||
@@ -427,7 +428,7 @@ describe('AppInsightsDatasource', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of group bys', () => {
|
it('should return a list of group bys', () => {
|
||||||
return ctx.ds.getAppInsightsMetricMetadata('requests/count').then(results => {
|
return ctx.ds.getAppInsightsMetricMetadata('requests/count').then((results: any) => {
|
||||||
expect(results.primaryAggType).toEqual('avg');
|
expect(results.primaryAggType).toEqual('avg');
|
||||||
expect(results.supportedAggTypes).toContain('avg');
|
expect(results.supportedAggTypes).toContain('avg');
|
||||||
expect(results.supportedAggTypes).toContain('sum');
|
expect(results.supportedAggTypes).toContain('sum');
|
||||||
|
|||||||
+15
-13
@@ -6,6 +6,7 @@ import { DataSourceInstanceSettings } from '@grafana/ui';
|
|||||||
import { AzureDataSourceJsonData } from '../types';
|
import { AzureDataSourceJsonData } from '../types';
|
||||||
import { BackendSrv } from 'app/core/services/backend_srv';
|
import { BackendSrv } from 'app/core/services/backend_srv';
|
||||||
import { TemplateSrv } from 'app/features/templating/template_srv';
|
import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||||
|
import { IQService } from 'angular';
|
||||||
|
|
||||||
export interface LogAnalyticsColumn {
|
export interface LogAnalyticsColumn {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -24,7 +25,7 @@ export default class AppInsightsDatasource {
|
|||||||
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
instanceSettings: DataSourceInstanceSettings<AzureDataSourceJsonData>,
|
||||||
private backendSrv: BackendSrv,
|
private backendSrv: BackendSrv,
|
||||||
private templateSrv: TemplateSrv,
|
private templateSrv: TemplateSrv,
|
||||||
private $q
|
private $q: IQService
|
||||||
) {
|
) {
|
||||||
this.id = instanceSettings.id;
|
this.id = instanceSettings.id;
|
||||||
this.applicationId = instanceSettings.jsonData.appInsightsAppId;
|
this.applicationId = instanceSettings.jsonData.appInsightsAppId;
|
||||||
@@ -36,7 +37,7 @@ export default class AppInsightsDatasource {
|
|||||||
return !!this.applicationId && this.applicationId.length > 0;
|
return !!this.applicationId && this.applicationId.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
query(options) {
|
query(options: any) {
|
||||||
const queries = _.filter(options.targets, item => {
|
const queries = _.filter(options.targets, item => {
|
||||||
return item.hide !== true;
|
return item.hide !== true;
|
||||||
}).map(target => {
|
}).map(target => {
|
||||||
@@ -106,6 +107,7 @@ export default class AppInsightsDatasource {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!queries || queries.length === 0) {
|
if (!queries || queries.length === 0) {
|
||||||
|
// @ts-ignore
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,16 +132,16 @@ export default class AppInsightsDatasource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doQueries(queries) {
|
doQueries(queries: any) {
|
||||||
return _.map(queries, query => {
|
return _.map(queries, query => {
|
||||||
return this.doRequest(query.url)
|
return this.doRequest(query.url)
|
||||||
.then(result => {
|
.then((result: any) => {
|
||||||
return {
|
return {
|
||||||
result: result,
|
result: result,
|
||||||
query: query,
|
query: query,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err: any) => {
|
||||||
throw {
|
throw {
|
||||||
error: err,
|
error: err,
|
||||||
query: query,
|
query: query,
|
||||||
@@ -148,7 +150,7 @@ export default class AppInsightsDatasource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
annotationQuery(options) {}
|
annotationQuery(options: any) {}
|
||||||
|
|
||||||
metricFindQuery(query: string) {
|
metricFindQuery(query: string) {
|
||||||
const appInsightsMetricNameQuery = query.match(/^AppInsightsMetricNames\(\)/i);
|
const appInsightsMetricNameQuery = query.match(/^AppInsightsMetricNames\(\)/i);
|
||||||
@@ -168,7 +170,7 @@ export default class AppInsightsDatasource {
|
|||||||
testDatasource() {
|
testDatasource() {
|
||||||
const url = `${this.baseUrl}/metrics/metadata`;
|
const url = `${this.baseUrl}/metrics/metadata`;
|
||||||
return this.doRequest(url)
|
return this.doRequest(url)
|
||||||
.then(response => {
|
.then((response: any) => {
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
return {
|
return {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
@@ -182,7 +184,7 @@ export default class AppInsightsDatasource {
|
|||||||
message: 'Returned http status code ' + response.status,
|
message: 'Returned http status code ' + response.status,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error: any) => {
|
||||||
let message = 'Application Insights: ';
|
let message = 'Application Insights: ';
|
||||||
message += error.statusText ? error.statusText + ': ' : '';
|
message += error.statusText ? error.statusText + ': ' : '';
|
||||||
|
|
||||||
@@ -201,13 +203,13 @@ export default class AppInsightsDatasource {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doRequest(url, maxRetries = 1) {
|
doRequest(url: any, maxRetries = 1) {
|
||||||
return this.backendSrv
|
return this.backendSrv
|
||||||
.datasourceRequest({
|
.datasourceRequest({
|
||||||
url: this.url + url,
|
url: this.url + url,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error: any) => {
|
||||||
if (maxRetries > 0) {
|
if (maxRetries > 0) {
|
||||||
return this.doRequest(url, maxRetries - 1);
|
return this.doRequest(url, maxRetries - 1);
|
||||||
}
|
}
|
||||||
@@ -223,20 +225,20 @@ export default class AppInsightsDatasource {
|
|||||||
|
|
||||||
getMetricMetadata(metricName: string) {
|
getMetricMetadata(metricName: string) {
|
||||||
const url = `${this.baseUrl}/metrics/metadata`;
|
const url = `${this.baseUrl}/metrics/metadata`;
|
||||||
return this.doRequest(url).then(result => {
|
return this.doRequest(url).then((result: any) => {
|
||||||
return new ResponseParser(result).parseMetadata(metricName);
|
return new ResponseParser(result).parseMetadata(metricName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroupBys(metricName: string) {
|
getGroupBys(metricName: string) {
|
||||||
return this.getMetricMetadata(metricName).then(result => {
|
return this.getMetricMetadata(metricName).then((result: any) => {
|
||||||
return new ResponseParser(result).parseGroupBys();
|
return new ResponseParser(result).parseGroupBys();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getQuerySchema() {
|
getQuerySchema() {
|
||||||
const url = `${this.baseUrl}/query/schema`;
|
const url = `${this.baseUrl}/query/schema`;
|
||||||
return this.doRequest(url).then(result => {
|
return this.doRequest(url).then((result: any) => {
|
||||||
const schema = new ResponseParser(result).parseQuerySchema();
|
const schema = new ResponseParser(result).parseQuerySchema();
|
||||||
// console.log(schema);
|
// console.log(schema);
|
||||||
return schema;
|
return schema;
|
||||||
|
|||||||
+1
-1
@@ -162,7 +162,7 @@ export default class FakeSchemaData {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static getlogAnalyticsFakeMetadata() {
|
static getlogAnalyticsFakeMetadata(): any {
|
||||||
return {
|
return {
|
||||||
tables: [
|
tables: [
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
export default class SupportedNamespaces {
|
export default class SupportedNamespaces {
|
||||||
supportedMetricNamespaces = {
|
supportedMetricNamespaces: any = {
|
||||||
azuremonitor: [
|
azuremonitor: [
|
||||||
'Microsoft.AnalysisServices/servers',
|
'Microsoft.AnalysisServices/servers',
|
||||||
'Microsoft.ApiManagement/service',
|
'Microsoft.ApiManagement/service',
|
||||||
|
|||||||
+3
-1
@@ -43,7 +43,7 @@ const defaultSchema: any = () => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const cleanText = s => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim();
|
const cleanText = (s: string) => s.replace(/[{}[\]="(),!~+\-*/^%]/g, '').trim();
|
||||||
const wrapText = (text: string) => ({ text });
|
const wrapText = (text: string) => ({ text });
|
||||||
|
|
||||||
export default class KustoQueryField extends QueryField {
|
export default class KustoQueryField extends QueryField {
|
||||||
@@ -353,11 +353,13 @@ export default class KustoQueryField extends QueryField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTableSuggestions(db = 'Default'): SuggestionGroup[] {
|
getTableSuggestions(db = 'Default'): SuggestionGroup[] {
|
||||||
|
// @ts-ignore
|
||||||
if (this.schema.Databases[db]) {
|
if (this.schema.Databases[db]) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
prefixMatch: true,
|
prefixMatch: true,
|
||||||
label: 'Tables',
|
label: 'Tables',
|
||||||
|
// @ts-ignore
|
||||||
items: _.map(this.schema.Databases[db].Tables, (t: any) => ({ text: t.Name })),
|
items: _.map(this.schema.Databases[db].Tables, (t: any) => ({ text: t.Name })),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ describe('Graphite query model', () => {
|
|||||||
waitForFuncDefsLoaded: jest.fn().mockReturnValue(Promise.resolve(null)),
|
waitForFuncDefsLoaded: jest.fn().mockReturnValue(Promise.resolve(null)),
|
||||||
createFuncInstance: gfunc.createFuncInstance,
|
createFuncInstance: gfunc.createFuncInstance,
|
||||||
},
|
},
|
||||||
|
// @ts-ignore
|
||||||
templateSrv: new TemplateSrvStub(),
|
templateSrv: new TemplateSrvStub(),
|
||||||
targets: [],
|
targets: [],
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user