From 20b2b344f6b230887f9f0625cc10485ccc29dde1 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Sat, 28 Jul 2018 11:31:30 +0200 Subject: [PATCH 01/62] mssql: add logo --- .../datasource/mssql/img/sql_server_logo.svg | 115 ++++++++++++++++++ .../app/plugins/datasource/mssql/plugin.json | 4 +- 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 public/app/plugins/datasource/mssql/img/sql_server_logo.svg diff --git a/public/app/plugins/datasource/mssql/img/sql_server_logo.svg b/public/app/plugins/datasource/mssql/img/sql_server_logo.svg new file mode 100644 index 00000000000..7fb7859c8ac --- /dev/null +++ b/public/app/plugins/datasource/mssql/img/sql_server_logo.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/public/app/plugins/datasource/mssql/plugin.json b/public/app/plugins/datasource/mssql/plugin.json index 65ef82511cd..ac5ea49ebe9 100644 --- a/public/app/plugins/datasource/mssql/plugin.json +++ b/public/app/plugins/datasource/mssql/plugin.json @@ -10,8 +10,8 @@ "url": "https://grafana.com" }, "logos": { - "small": "", - "large": "" + "small": "img/sql_server_logo.svg", + "large": "img/sql_server_logo.svg" } }, From 62d3655da43d712e32c1cb2f1a406c157e477478 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Thu, 26 Jul 2018 13:40:52 +0200 Subject: [PATCH 02/62] docker: inital copy of the grafana-docker files. --- .circleci/config.yml | 28 ++++++++- packaging/docker/Dockerfile | 38 ++++++++++++ packaging/docker/build.sh | 22 +++++++ packaging/docker/push_to_docker_hub.sh | 17 ++++++ packaging/docker/run.sh | 82 ++++++++++++++++++++++++++ 5 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 packaging/docker/Dockerfile create mode 100755 packaging/docker/build.sh create mode 100755 packaging/docker/push_to_docker_hub.sh create mode 100755 packaging/docker/run.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 44f34d42926..01cd36261fc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -89,7 +89,7 @@ jobs: name: run linters command: 'gometalinter.v2 --enable-gc --vendor --deadline 10m --disable-all --enable=deadcode --enable=ineffassign --enable=structcheck --enable=unconvert --enable=varcheck ./...' - run: - name: run go vet + name: run go vet command: 'go vet ./pkg/...' test-frontend: @@ -159,6 +159,16 @@ jobs: - store_artifacts: path: dist + build-deploy-docker-master: + docker: + - image: docker:stable-git + steps: + - checkout + - setup_remote_docker + - run: docker info + - run: echo $GRAFANA_VERSION + - run: ./build.sh ${GRAFANA_VERSION} + build-enterprise: docker: - image: grafana/build-container:v0.1 @@ -246,7 +256,7 @@ workflows: test-and-build: jobs: - build-all: - filters: *filter-only-master + filters: *filter-not-release - build-enterprise: filters: *filter-only-master - codespell: @@ -270,7 +280,19 @@ workflows: - gometalinter - mysql-integration-test - postgres-integration-test - filters: *filter-only-master + filters: *filter-only-master + - build-deploy-docker-master: + requires: + - build-all + - test-backend + - test-frontend + - codespell + - gometalinter + - mysql-integration-test + - postgres-integration-test + filters: + branches: + only: grafana-docker - deploy-enterprise-master: requires: - build-all diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile new file mode 100644 index 00000000000..6e4a5896b75 --- /dev/null +++ b/packaging/docker/Dockerfile @@ -0,0 +1,38 @@ +FROM debian:stretch-slim + +ARG GRAFANA_URL="https://s3-us-west-2.amazonaws.com/grafana-releases/master/grafana-latest.linux-x64.tar.gz" +ARG GF_UID="472" +ARG GF_GID="472" + +ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + GF_PATHS_CONFIG="/etc/grafana/grafana.ini" \ + GF_PATHS_DATA="/var/lib/grafana" \ + GF_PATHS_HOME="/usr/share/grafana" \ + GF_PATHS_LOGS="/var/log/grafana" \ + GF_PATHS_PLUGINS="/var/lib/grafana/plugins" \ + GF_PATHS_PROVISIONING="/etc/grafana/provisioning" + +RUN apt-get update && apt-get install -qq -y tar libfontconfig curl ca-certificates && \ + mkdir -p "$GF_PATHS_HOME/.aws" && \ + curl "$GRAFANA_URL" | tar xfvz - --strip-components=1 -C "$GF_PATHS_HOME" && \ + apt-get autoremove -y && \ + rm -rf /var/lib/apt/lists/* && \ + groupadd -r -g $GF_GID grafana && \ + useradd -r -u $GF_UID -g grafana grafana && \ + mkdir -p "$GF_PATHS_PROVISIONING/datasources" \ + "$GF_PATHS_PROVISIONING/dashboards" \ + "$GF_PATHS_LOGS" \ + "$GF_PATHS_PLUGINS" \ + "$GF_PATHS_DATA" && \ + cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \ + cp "$GF_PATHS_HOME/conf/ldap.toml" /etc/grafana/ldap.toml && \ + chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" && \ + chmod 777 "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" + +EXPOSE 3000 + +COPY ./run.sh /run.sh + +USER grafana +WORKDIR / +ENTRYPOINT [ "/run.sh" ] \ No newline at end of file diff --git a/packaging/docker/build.sh b/packaging/docker/build.sh new file mode 100755 index 00000000000..ac1dd41feec --- /dev/null +++ b/packaging/docker/build.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +_grafana_tag=$1 +_grafana_version=$(echo ${_grafana_tag} | cut -d "v" -f 2) +_docker_repo=${2:-grafana/grafana} + + +echo ${_grafana_version} + +if [ "$_grafana_version" != "" ]; then + echo "Building version ${_grafana_version}" + docker build \ + --build-arg GRAFANA_URL="https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-${_grafana_version}.linux-amd64.tar.gz" \ + --tag "${_docker_repo}:${_grafana_version}" \ + --no-cache=true . + docker tag ${_docker_repo}:${_grafana_version} ${_docker_repo}:latest +else + echo "Building latest for master" + docker build \ + --tag "grafana/grafana:master" \ + . +fi diff --git a/packaging/docker/push_to_docker_hub.sh b/packaging/docker/push_to_docker_hub.sh new file mode 100755 index 00000000000..4b23996f67f --- /dev/null +++ b/packaging/docker/push_to_docker_hub.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +_grafana_tag=$1 +_grafana_version=$(echo ${_grafana_tag} | cut -d "v" -f 2) + +if [ "$_grafana_version" != "" ]; then + echo "pushing grafana/grafana:${_grafana_version}" + docker push grafana/grafana:${_grafana_version} + + if echo "$_grafana_version" | grep -viqF beta; then + echo "pushing grafana/grafana:latest" + docker push grafana/grafana:latest + fi +else + echo "pushing grafana/grafana:master" + docker push grafana/grafana:master +fi diff --git a/packaging/docker/run.sh b/packaging/docker/run.sh new file mode 100755 index 00000000000..44411f0f6b6 --- /dev/null +++ b/packaging/docker/run.sh @@ -0,0 +1,82 @@ +#!/bin/bash -e + +PERMISSIONS_OK=0 + +if [ ! -r "$GF_PATHS_CONFIG" ]; then + echo "GF_PATHS_CONFIG='$GF_PATHS_CONFIG' is not readable." + PERMISSIONS_OK=1 +fi + +if [ ! -w "$GF_PATHS_DATA" ]; then + echo "GF_PATHS_DATA='$GF_PATHS_DATA' is not writable." + PERMISSIONS_OK=1 +fi + +if [ ! -r "$GF_PATHS_HOME" ]; then + echo "GF_PATHS_HOME='$GF_PATHS_HOME' is not readable." + PERMISSIONS_OK=1 +fi + +if [ $PERMISSIONS_OK -eq 1 ]; then + echo "You may have issues with file permissions, more information here: http://docs.grafana.org/installation/docker/#migration-from-a-previous-version-of-the-docker-container-to-5-1-or-later" +fi + +if [ ! -d "$GF_PATHS_PLUGINS" ]; then + mkdir "$GF_PATHS_PLUGINS" +fi + +if [ ! -z ${GF_AWS_PROFILES+x} ]; then + > "$GF_PATHS_HOME/.aws/credentials" + + for profile in ${GF_AWS_PROFILES}; do + access_key_varname="GF_AWS_${profile}_ACCESS_KEY_ID" + secret_key_varname="GF_AWS_${profile}_SECRET_ACCESS_KEY" + region_varname="GF_AWS_${profile}_REGION" + + if [ ! -z "${!access_key_varname}" -a ! -z "${!secret_key_varname}" ]; then + echo "[${profile}]" >> "$GF_PATHS_HOME/.aws/credentials" + echo "aws_access_key_id = ${!access_key_varname}" >> "$GF_PATHS_HOME/.aws/credentials" + echo "aws_secret_access_key = ${!secret_key_varname}" >> "$GF_PATHS_HOME/.aws/credentials" + if [ ! -z "${!region_varname}" ]; then + echo "region = ${!region_varname}" >> "$GF_PATHS_HOME/.aws/credentials" + fi + fi + done + + chmod 600 "$GF_PATHS_HOME/.aws/credentials" +fi + +# Convert all environment variables with names ending in _FILE into the content of +# the file that they point at and use the name without the trailing _FILE. +# This can be used to carry in Docker secrets. +for VAR_NAME in $(env | grep '^GF_[^=]\+_FILE=.\+' | sed -r "s/([^=]*)_FILE=.*/\1/g"); do + VAR_NAME_FILE="$VAR_NAME"_FILE + if [ "${!VAR_NAME}" ]; then + echo >&2 "ERROR: Both $VAR_NAME and $VAR_NAME_FILE are set (but are exclusive)" + exit 1 + fi + echo "Getting secret $VAR_NAME from ${!VAR_NAME_FILE}" + export "$VAR_NAME"="$(< "${!VAR_NAME_FILE}")" + unset "$VAR_NAME_FILE" +done + +export HOME="$GF_PATHS_HOME" + +if [ ! -z "${GF_INSTALL_PLUGINS}" ]; then + OLDIFS=$IFS + IFS=',' + for plugin in ${GF_INSTALL_PLUGINS}; do + IFS=$OLDIFS + grafana-cli --pluginsDir "${GF_PATHS_PLUGINS}" plugins install ${plugin} + done +fi + +exec grafana-server \ + --homepath="$GF_PATHS_HOME" \ + --config="$GF_PATHS_CONFIG" \ + "$@" \ + cfg:default.log.mode="console" \ + cfg:default.paths.data="$GF_PATHS_DATA" \ + cfg:default.paths.logs="$GF_PATHS_LOGS" \ + cfg:default.paths.plugins="$GF_PATHS_PLUGINS" \ + cfg:default.paths.provisioning="$GF_PATHS_PROVISIONING" From bfe41d3cf15654f86e7c879b8e927f4daeaacff5 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Thu, 26 Jul 2018 16:46:36 +0200 Subject: [PATCH 03/62] build: new workflow for PR:s and branches. --- .circleci/config.yml | 104 ++++++++++++++++++++++++++++------------- scripts/build/build.sh | 22 ++++----- 2 files changed, 81 insertions(+), 45 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 01cd36261fc..6dc3cdf378b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,9 +5,11 @@ aliases: ignore: /.*/ tags: only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ - - &filter-not-release + - &filter-not-release-or-master tags: ignore: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/ + branches: + ignore: master - &filter-only-master branches: only: master @@ -156,18 +158,39 @@ jobs: - dist/grafana* - scripts/*.sh - scripts/publish - - store_artifacts: - path: dist - build-deploy-docker-master: - docker: - - image: docker:stable-git - steps: - - checkout - - setup_remote_docker - - run: docker info - - run: echo $GRAFANA_VERSION - - run: ./build.sh ${GRAFANA_VERSION} + build: + docker: + - image: grafana/build-container:1.0.0 + working_directory: /go/src/github.com/grafana/grafana + steps: + - checkout + - run: + name: prepare build tools + command: '/tmp/bootstrap.sh' + - run: + name: build and package grafana + command: './scripts/build/build.sh' + - run: + name: sign packages + command: './scripts/build/sign_packages.sh' + - run: + name: sha-sum packages + command: 'go run build.go sha-dist' + - persist_to_workspace: + root: . + paths: + - dist/grafana* + + build-docker: + docker: + - image: docker:stable-git + steps: + - checkout + - setup_remote_docker + - run: docker info + - run: echo $GRAFANA_VERSION + - run: cd packaging/docker && ./build.sh ${GRAFANA_VERSION} build-enterprise: docker: @@ -253,24 +276,24 @@ jobs: workflows: version: 2 - test-and-build: + build-master: jobs: - build-all: - filters: *filter-not-release + filters: *filter-only-master - build-enterprise: filters: *filter-only-master - codespell: - filters: *filter-not-release + filters: *filter-only-master - gometalinter: - filters: *filter-not-release + filters: *filter-only-master - test-frontend: - filters: *filter-not-release + filters: *filter-only-master - test-backend: - filters: *filter-not-release + filters: *filter-only-master - mysql-integration-test: - filters: *filter-not-release + filters: *filter-only-master - postgres-integration-test: - filters: *filter-not-release + filters: *filter-only-master - deploy-master: requires: - build-all @@ -281,18 +304,6 @@ workflows: - mysql-integration-test - postgres-integration-test filters: *filter-only-master - - build-deploy-docker-master: - requires: - - build-all - - test-backend - - test-frontend - - codespell - - gometalinter - - mysql-integration-test - - postgres-integration-test - filters: - branches: - only: grafana-docker - deploy-enterprise-master: requires: - build-all @@ -331,3 +342,32 @@ workflows: - mysql-integration-test - postgres-integration-test filters: *filter-only-release + + build-branches-and-prs: + jobs: + - build: + filters: *filter-not-release-or-master + - codespell: + filters: *filter-not-release-or-master + - gometalinter: + filters: *filter-not-release-or-master + - test-frontend: + filters: *filter-not-release-or-master + - test-backend: + filters: *filter-not-release-or-master + - mysql-integration-test: + filters: *filter-not-release-or-master + - postgres-integration-test: + filters: *filter-not-release-or-master + - build-docker: + requires: + - build + - test-backend + - test-frontend + - codespell + - gometalinter + - mysql-integration-test + - postgres-integration-test + filters: + branches: + only: grafana-docker diff --git a/scripts/build/build.sh b/scripts/build/build.sh index cee80822cac..a02f079dd72 100755 --- a/scripts/build/build.sh +++ b/scripts/build/build.sh @@ -14,12 +14,14 @@ echo "current dir: $(pwd)" if [ "$CIRCLE_TAG" != "" ]; then echo "Building releases from tag $CIRCLE_TAG" - CC=${CCX64} go run build.go -includeBuildNumber=false build + OPT="-includeBuildNumber=false" else echo "Building incremental build for $CIRCLE_BRANCH" - CC=${CCX64} go run build.go -buildNumber=${CIRCLE_BUILD_NUM} build + OPT="-buildNumber=${CIRCLE_BUILD_NUM}" fi +CC=${CCX64} go run build.go ${OPT} build + yarn install --pure-lockfile --no-progress echo "current dir: $(pwd)" @@ -28,14 +30,8 @@ if [ -d "dist" ]; then rm -rf dist fi -if [ "$CIRCLE_TAG" != "" ]; then - echo "Building frontend from tag $CIRCLE_TAG" - go run build.go -includeBuildNumber=false build-frontend - echo "Packaging a release from tag $CIRCLE_TAG" - go run build.go -goos linux -pkg-arch amd64 -includeBuildNumber=false package-only latest -else - echo "Building frontend for $CIRCLE_BRANCH" - go run build.go -buildNumber=${CIRCLE_BUILD_NUM} build-frontend - echo "Packaging incremental build for $CIRCLE_BRANCH" - go run build.go -goos linux -pkg-arch amd64 -buildNumber=${CIRCLE_BUILD_NUM} package-only latest -fi +echo "Building frontend" +go run build.go ${OPT} build-frontend + +echo "Packaging" +go run build.go -goos linux -pkg-arch amd64 ${OPT} package-only latest From e3a907214d822fd4db0f89cf1489165b742ec7ad Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Sat, 28 Jul 2018 23:00:59 +0200 Subject: [PATCH 04/62] build: builds docker image from local grafna tgz. --- .circleci/config.yml | 1 + packaging/docker/Dockerfile | 11 +++++++---- packaging/docker/build.sh | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 6dc3cdf378b..74c4ee6c3ef 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -190,6 +190,7 @@ jobs: - setup_remote_docker - run: docker info - run: echo $GRAFANA_VERSION + - run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker - run: cd packaging/docker && ./build.sh ${GRAFANA_VERSION} build-enterprise: diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index 6e4a5896b75..3025b03f920 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -1,6 +1,6 @@ FROM debian:stretch-slim -ARG GRAFANA_URL="https://s3-us-west-2.amazonaws.com/grafana-releases/master/grafana-latest.linux-x64.tar.gz" +ARG GRAFANA_TGZ="grafana-latest.linux-x64.tar.gz" ARG GF_UID="472" ARG GF_GID="472" @@ -12,9 +12,12 @@ ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bi GF_PATHS_PLUGINS="/var/lib/grafana/plugins" \ GF_PATHS_PROVISIONING="/etc/grafana/provisioning" -RUN apt-get update && apt-get install -qq -y tar libfontconfig curl ca-certificates && \ +COPY ${GRAFANA_TGZ} /tmp/grafana.tar.gz + +RUN apt-get update && apt-get install -qq -y tar libfontconfig ca-certificates && \ mkdir -p "$GF_PATHS_HOME/.aws" && \ - curl "$GRAFANA_URL" | tar xfvz - --strip-components=1 -C "$GF_PATHS_HOME" && \ + tar xfvz /tmp/grafana.tar.gz --strip-components=1 -C "$GF_PATHS_HOME" && \ + rm /tmp/grafana.tar.gz && \ apt-get autoremove -y && \ rm -rf /var/lib/apt/lists/* && \ groupadd -r -g $GF_GID grafana && \ @@ -35,4 +38,4 @@ COPY ./run.sh /run.sh USER grafana WORKDIR / -ENTRYPOINT [ "/run.sh" ] \ No newline at end of file +ENTRYPOINT [ "/run.sh" ] diff --git a/packaging/docker/build.sh b/packaging/docker/build.sh index ac1dd41feec..df0a809c754 100755 --- a/packaging/docker/build.sh +++ b/packaging/docker/build.sh @@ -10,7 +10,6 @@ echo ${_grafana_version} if [ "$_grafana_version" != "" ]; then echo "Building version ${_grafana_version}" docker build \ - --build-arg GRAFANA_URL="https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-${_grafana_version}.linux-amd64.tar.gz" \ --tag "${_docker_repo}:${_grafana_version}" \ --no-cache=true . docker tag ${_docker_repo}:${_grafana_version} ${_docker_repo}:latest From e8489304760d781498d49b31bbfb90515382c9f8 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Sun, 29 Jul 2018 12:04:31 +0200 Subject: [PATCH 05/62] build: attach built resources. --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 74c4ee6c3ef..5fe09fbb349 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -187,6 +187,8 @@ jobs: - image: docker:stable-git steps: - checkout + - attach_workspace: + at: . - setup_remote_docker - run: docker info - run: echo $GRAFANA_VERSION From 580e2c36d1575d205aad8b3c33f3d1b8b90a9b41 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 30 Jul 2018 14:05:56 +0200 Subject: [PATCH 06/62] build: imported latest changes from grafana-docker. --- .circleci/config.yml | 7 +++-- packaging/docker/build-deploy.sh | 13 +++++++++ packaging/docker/build.sh | 37 +++++++++++++++----------- packaging/docker/deploy_to_k8s.sh | 6 +++++ packaging/docker/push_to_docker_hub.sh | 22 +++++++++------ packaging/docker/run.sh | 8 +++--- 6 files changed, 62 insertions(+), 31 deletions(-) create mode 100755 packaging/docker/build-deploy.sh create mode 100755 packaging/docker/deploy_to_k8s.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 5fe09fbb349..d59e4984454 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -182,7 +182,7 @@ jobs: paths: - dist/grafana* - build-docker: + grafana-docker-master: docker: - image: docker:stable-git steps: @@ -191,9 +191,8 @@ jobs: at: . - setup_remote_docker - run: docker info - - run: echo $GRAFANA_VERSION - run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker - - run: cd packaging/docker && ./build.sh ${GRAFANA_VERSION} + - run: cd packaging/docker && ./build-deploy.sh "grafana-docker-${CIRCLE_SHA1}" build-enterprise: docker: @@ -362,7 +361,7 @@ workflows: filters: *filter-not-release-or-master - postgres-integration-test: filters: *filter-not-release-or-master - - build-docker: + - grafana-docker-master: requires: - build - test-backend diff --git a/packaging/docker/build-deploy.sh b/packaging/docker/build-deploy.sh new file mode 100755 index 00000000000..923b1b8f3c0 --- /dev/null +++ b/packaging/docker/build-deploy.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +_grafana_version=$1 +./build.sh "$_grafana_version" +docker login -u "$DOCKER_USER" -p "$DOCKER_PASS" + +#./push_to_docker_hub.sh "$_grafana_version" +echo "Would have deployed $_grafana_version" + +if echo "$_grafana_version" | grep -q "^master-"; then + apk add --no-cache curl + ./deploy_to_k8s.sh "grafana/grafana-dev:$_grafana_version" +fi diff --git a/packaging/docker/build.sh b/packaging/docker/build.sh index df0a809c754..579d65eebb3 100755 --- a/packaging/docker/build.sh +++ b/packaging/docker/build.sh @@ -1,21 +1,28 @@ #!/bin/sh _grafana_tag=$1 -_grafana_version=$(echo ${_grafana_tag} | cut -d "v" -f 2) -_docker_repo=${2:-grafana/grafana} - -echo ${_grafana_version} - -if [ "$_grafana_version" != "" ]; then - echo "Building version ${_grafana_version}" - docker build \ - --tag "${_docker_repo}:${_grafana_version}" \ - --no-cache=true . - docker tag ${_docker_repo}:${_grafana_version} ${_docker_repo}:latest +# If the tag starts with v, treat this as a official release +if echo "$_grafana_tag" | grep -q "^v"; then + _grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2) + _grafana_url="https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-${_grafana_version}.linux-amd64.tar.gz" + _docker_repo=${2:-grafana/grafana} else - echo "Building latest for master" - docker build \ - --tag "grafana/grafana:master" \ - . + _grafana_version=$_grafana_tag + _grafana_url="https://s3-us-west-2.amazonaws.com/grafana-releases/master/grafana-${_grafana_version}.linux-x64.tar.gz" + _docker_repo=${2:-grafana/grafana-dev} +fi + +echo "Building ${_docker_repo}:${_grafana_version} from ${_grafana_url}" + +docker build \ + --build-arg GRAFANA_URL="${_grafana_url}" \ + --tag "${_docker_repo}:${_grafana_version}" \ + --no-cache=true . + +# Tag as 'latest' for official release; otherwise tag as grafana/grafana:master +if echo "$_grafana_tag" | grep -q "^v"; then + docker tag "${_docker_repo}:${_grafana_version}" "${_docker_repo}:latest" +else + docker tag "${_docker_repo}:${_grafana_version}" "grafana/grafana:master" fi diff --git a/packaging/docker/deploy_to_k8s.sh b/packaging/docker/deploy_to_k8s.sh new file mode 100755 index 00000000000..26cf88ef688 --- /dev/null +++ b/packaging/docker/deploy_to_k8s.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +curl -s --header "Content-Type: application/json" \ + --data "{\"build_parameters\": {\"CIRCLE_JOB\": \"deploy\", \"IMAGE_NAMES\": \"$1\"}}" \ + --request POST \ + https://circleci.com/api/v1.1/project/github/raintank/deployment_tools/tree/master?circle-token=$CIRCLE_TOKEN diff --git a/packaging/docker/push_to_docker_hub.sh b/packaging/docker/push_to_docker_hub.sh index 4b23996f67f..e779b04d68d 100755 --- a/packaging/docker/push_to_docker_hub.sh +++ b/packaging/docker/push_to_docker_hub.sh @@ -1,16 +1,22 @@ #!/bin/sh _grafana_tag=$1 -_grafana_version=$(echo ${_grafana_tag} | cut -d "v" -f 2) -if [ "$_grafana_version" != "" ]; then - echo "pushing grafana/grafana:${_grafana_version}" - docker push grafana/grafana:${_grafana_version} +# If the tag starts with v, treat this as a official release +if echo "$_grafana_tag" | grep -q "^v"; then + _grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2) + _docker_repo=${2:-grafana/grafana} +else + _grafana_version=$_grafana_tag + _docker_repo=${2:-grafana/grafana-dev} +fi - if echo "$_grafana_version" | grep -viqF beta; then - echo "pushing grafana/grafana:latest" - docker push grafana/grafana:latest - fi +echo "pushing ${_docker_repo}:${_grafana_version}" +docker push "${_docker_repo}:${_grafana_version}" + +if echo "$_grafana_tag" | grep -q "^v"; then + echo "pushing ${_docker_repo}:latest" + docker push "${_docker_repo}:latest" else echo "pushing grafana/grafana:master" docker push grafana/grafana:master diff --git a/packaging/docker/run.sh b/packaging/docker/run.sh index 44411f0f6b6..2d2318a9210 100755 --- a/packaging/docker/run.sh +++ b/packaging/docker/run.sh @@ -46,11 +46,11 @@ if [ ! -z ${GF_AWS_PROFILES+x} ]; then chmod 600 "$GF_PATHS_HOME/.aws/credentials" fi -# Convert all environment variables with names ending in _FILE into the content of -# the file that they point at and use the name without the trailing _FILE. +# Convert all environment variables with names ending in __FILE into the content of +# the file that they point at and use the name without the trailing __FILE. # This can be used to carry in Docker secrets. -for VAR_NAME in $(env | grep '^GF_[^=]\+_FILE=.\+' | sed -r "s/([^=]*)_FILE=.*/\1/g"); do - VAR_NAME_FILE="$VAR_NAME"_FILE +for VAR_NAME in $(env | grep '^GF_[^=]\+__FILE=.\+' | sed -r "s/([^=]*)__FILE=.*/\1/g"); do + VAR_NAME_FILE="$VAR_NAME"__FILE if [ "${!VAR_NAME}" ]; then echo >&2 "ERROR: Both $VAR_NAME and $VAR_NAME_FILE are set (but are exclusive)" exit 1 From 424aa6e564fc6419c9192a4ee6cf74550f5aad67 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 30 Jul 2018 16:35:30 +0200 Subject: [PATCH 07/62] build: removes unused args to docker build. --- packaging/docker/build.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packaging/docker/build.sh b/packaging/docker/build.sh index 579d65eebb3..c303c71cd5f 100755 --- a/packaging/docker/build.sh +++ b/packaging/docker/build.sh @@ -5,18 +5,15 @@ _grafana_tag=$1 # If the tag starts with v, treat this as a official release if echo "$_grafana_tag" | grep -q "^v"; then _grafana_version=$(echo "${_grafana_tag}" | cut -d "v" -f 2) - _grafana_url="https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana-${_grafana_version}.linux-amd64.tar.gz" _docker_repo=${2:-grafana/grafana} else _grafana_version=$_grafana_tag - _grafana_url="https://s3-us-west-2.amazonaws.com/grafana-releases/master/grafana-${_grafana_version}.linux-x64.tar.gz" _docker_repo=${2:-grafana/grafana-dev} fi -echo "Building ${_docker_repo}:${_grafana_version} from ${_grafana_url}" +echo "Building ${_docker_repo}:${_grafana_version}" docker build \ - --build-arg GRAFANA_URL="${_grafana_url}" \ --tag "${_docker_repo}:${_grafana_version}" \ --no-cache=true . From 99a9dbb04f161eac59cc7450c0daf5934a70129a Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 30 Jul 2018 18:52:49 +0200 Subject: [PATCH 08/62] build: complete docker build for master and releases. --- .circleci/config.yml | 46 +++++++++++++++++++++--------- packaging/docker/README.md | 45 +++++++++++++++++++++++++++++ packaging/docker/build-deploy.sh | 3 +- packaging/docker/custom/Dockerfile | 16 +++++++++++ 4 files changed, 95 insertions(+), 15 deletions(-) create mode 100644 packaging/docker/README.md create mode 100644 packaging/docker/custom/Dockerfile diff --git a/.circleci/config.yml b/.circleci/config.yml index d59e4984454..818f30f7eea 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -192,7 +192,19 @@ jobs: - setup_remote_docker - run: docker info - run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker - - run: cd packaging/docker && ./build-deploy.sh "grafana-docker-${CIRCLE_SHA1}" + - run: cd packaging/docker && ./build-deploy.sh "master-${CIRCLE_SHA1}" + + grafana-docker-release: + docker: + - image: docker:stable-git + steps: + - checkout + - attach_workspace: + at: . + - setup_remote_docker + - run: docker info + - run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker + - run: cd packaging/docker && ./build-deploy.sh "${CIRCLE_TAG}" build-enterprise: docker: @@ -306,6 +318,16 @@ workflows: - mysql-integration-test - postgres-integration-test filters: *filter-only-master + - grafana-docker-master: + requires: + - build-all + - test-backend + - test-frontend + - codespell + - gometalinter + - mysql-integration-test + - postgres-integration-test + filters: *filter-only-master - deploy-enterprise-master: requires: - build-all @@ -344,6 +366,16 @@ workflows: - mysql-integration-test - postgres-integration-test filters: *filter-only-release + - grafana-docker-release: + requires: + - build-all + - test-backend + - test-frontend + - codespell + - gometalinter + - mysql-integration-test + - postgres-integration-test + filters: *filter-only-release build-branches-and-prs: jobs: @@ -361,15 +393,3 @@ workflows: filters: *filter-not-release-or-master - postgres-integration-test: filters: *filter-not-release-or-master - - grafana-docker-master: - requires: - - build - - test-backend - - test-frontend - - codespell - - gometalinter - - mysql-integration-test - - postgres-integration-test - filters: - branches: - only: grafana-docker diff --git a/packaging/docker/README.md b/packaging/docker/README.md new file mode 100644 index 00000000000..d80cd87aebc --- /dev/null +++ b/packaging/docker/README.md @@ -0,0 +1,45 @@ +# Grafana Docker image + +[![CircleCI](https://circleci.com/gh/grafana/grafana-docker.svg?style=svg)](https://circleci.com/gh/grafana/grafana-docker) + +## Running your Grafana container + +Start your container binding the external port `3000`. + +```bash +docker run -d --name=grafana -p 3000:3000 grafana/grafana +``` + +Try it out, default admin user is admin/admin. + +## How to use the container + +Further documentation can be found at http://docs.grafana.org/installation/docker/ + +## Changelog + +### v5.1.5, v5.2.0-beta2 +* Fix: config keys ending with _FILE are not respected [#170](https://github.com/grafana/grafana-docker/issues/170) + +### v5.2.0-beta1 +* Support for Docker Secrets + +### v5.1.0 +* Major restructuring of the container +* Usage of `chown` removed +* File permissions incompatibility with previous versions + * user id changed from 104 to 472 + * group id changed from 107 to 472 +* Runs as the grafana user by default (instead of root) +* All default volumes removed + +### v4.2.0 +* Plugins are now installed into ${GF_PATHS_PLUGINS} +* Building the container now requires a full url to the deb package instead of just version +* Fixes bug caused by installing multiple plugins + +### v4.0.0-beta2 +* Plugins dir (`/var/lib/grafana/plugins`) is no longer a separate volume + +### v3.1.1 +* Make it possible to install specific plugin version https://github.com/grafana/grafana-docker/issues/59#issuecomment-260584026 \ No newline at end of file diff --git a/packaging/docker/build-deploy.sh b/packaging/docker/build-deploy.sh index 923b1b8f3c0..e20ae2c2a41 100755 --- a/packaging/docker/build-deploy.sh +++ b/packaging/docker/build-deploy.sh @@ -4,8 +4,7 @@ _grafana_version=$1 ./build.sh "$_grafana_version" docker login -u "$DOCKER_USER" -p "$DOCKER_PASS" -#./push_to_docker_hub.sh "$_grafana_version" -echo "Would have deployed $_grafana_version" +./push_to_docker_hub.sh "$_grafana_version" if echo "$_grafana_version" | grep -q "^master-"; then apk add --no-cache curl diff --git a/packaging/docker/custom/Dockerfile b/packaging/docker/custom/Dockerfile new file mode 100644 index 00000000000..79eba5f29e9 --- /dev/null +++ b/packaging/docker/custom/Dockerfile @@ -0,0 +1,16 @@ +ARG GRAFANA_VERSION="latest" + +FROM grafana/grafana:${GRAFANA_VERSION} + +USER grafana + +ARG GF_INSTALL_PLUGINS="" + +RUN if [ ! -z "${GF_INSTALL_PLUGINS}" ]; then \ + OLDIFS=$IFS; \ + IFS=','; \ + for plugin in ${GF_INSTALL_PLUGINS}; do \ + IFS=$OLDIFS; \ + grafana-cli --pluginsDir "$GF_PATHS_PLUGINS" plugins install ${plugin}; \ + done; \ +fi From b61ac546f157a0b00d49ce11f5d86f8345034a38 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 30 Jul 2018 18:54:34 +0200 Subject: [PATCH 09/62] build: disables external docker build for master and release. --- .circleci/config.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 818f30f7eea..e2deab62c1b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -260,9 +260,6 @@ jobs: - run: name: Trigger Windows build command: './scripts/trigger_windows_build.sh ${APPVEYOR_TOKEN} ${CIRCLE_SHA1} master' - - run: - name: Trigger Docker build - command: './scripts/trigger_docker_build.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN} master-$(echo "${CIRCLE_SHA1}" | cut -b1-7)' - run: name: Publish to Grafana.com command: | @@ -284,9 +281,6 @@ jobs: - run: name: Trigger Windows build command: './scripts/trigger_windows_build.sh ${APPVEYOR_TOKEN} ${CIRCLE_SHA1} release' - - run: - name: Trigger Docker build - command: './scripts/trigger_docker_build.sh ${TRIGGER_GRAFANA_PACKER_CIRCLECI_TOKEN} ${CIRCLE_TAG}' workflows: version: 2 From bfc66a7ed0b395762eb72f4304569e4041711d8e Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Mon, 30 Jul 2018 11:04:04 +0200 Subject: [PATCH 10/62] add fillmode "last" to sql datasource This adds a new fill mode last (last observation carried forward) for grafana to the sql datasources. This fill mode will fill in the last seen value in a series when a timepoint is missing or NULL if no value for that series has been seen yet. --- docs/sources/features/datasources/mssql.md | 4 ++- docs/sources/features/datasources/mysql.md | 4 ++- docs/sources/features/datasources/postgres.md | 4 ++- pkg/tsdb/mssql/macros.go | 10 ++++-- pkg/tsdb/mssql/macros_test.go | 17 ++++++++-- pkg/tsdb/mysql/macros.go | 10 ++++-- pkg/tsdb/mysql/mysql_test.go | 31 ++++++++++++++++++- pkg/tsdb/postgres/macros.go | 10 ++++-- pkg/tsdb/postgres/postgres_test.go | 30 +++++++++++++++++- pkg/tsdb/sql_engine.go | 24 +++++++++++++- .../mssql/partials/query.editor.html | 4 ++- .../mysql/partials/query.editor.html | 4 ++- .../postgres/partials/query.editor.html | 4 ++- 13 files changed, 136 insertions(+), 20 deletions(-) diff --git a/docs/sources/features/datasources/mssql.md b/docs/sources/features/datasources/mssql.md index dabb896ec0f..524a93a943b 100644 --- a/docs/sources/features/datasources/mssql.md +++ b/docs/sources/features/datasources/mssql.md @@ -81,7 +81,9 @@ Macro example | Description *$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-04-21T05:01:17Z'* *$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-04-21T05:06:17Z'* *$__timeGroup(dateColumn,'5m'[, fillvalue])* | Will be replaced by an expression usable in GROUP BY clause. Providing a *fillValue* of *NULL* or *floating value* will automatically fill empty series in timerange with that value.
For example, *CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)\*300*. -*$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so all null values will be converted to the fill value (all null values would be set to zero using this example). +*$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value. +*$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points. +*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used. *$__timeGroupAlias(dateColumn,'5m')* | Will be replaced identical to $__timeGroup but with an added column alias (only available in Grafana 5.3+). *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* diff --git a/docs/sources/features/datasources/mysql.md b/docs/sources/features/datasources/mysql.md index a0e67037005..153b3d7bbf5 100644 --- a/docs/sources/features/datasources/mysql.md +++ b/docs/sources/features/datasources/mysql.md @@ -64,7 +64,9 @@ Macro example | Description *$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-04-21T05:01:17Z'* *$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-04-21T05:06:17Z'* *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),* -*$__timeGroup(dateColumn,'5m',0)* | Same as above but with a fill parameter so all null values will be converted to the fill value (all null values would be set to zero using this example). +*$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value. +*$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points. +*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used. *$__timeGroupAlias(dateColumn,'5m')* | Will be replaced identical to $__timeGroup but with an added column alias (only available in Grafana 5.3+). *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* diff --git a/docs/sources/features/datasources/postgres.md b/docs/sources/features/datasources/postgres.md index 35dfcac15c0..b776b7cbe58 100644 --- a/docs/sources/features/datasources/postgres.md +++ b/docs/sources/features/datasources/postgres.md @@ -61,7 +61,9 @@ Macro example | Description *$__timeFrom()* | Will be replaced by the start of the currently active time selection. For example, *'2017-04-21T05:01:17Z'* *$__timeTo()* | Will be replaced by the end of the currently active time selection. For example, *'2017-04-21T05:06:17Z'* *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from dateColumn)/300)::bigint*300* -*$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so all null values will be converted to the fill value (all null values would be set to zero using this example). +*$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value. +*$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points. +*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used. *$__timeGroupAlias(dateColumn,'5m')* | Will be replaced identical to $__timeGroup but with an added column alias (only available in Grafana 5.3+). *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn >= 1494410783 AND dateColumn <= 1494497183* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index f33ab1d40be..57a37d618e0 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -99,9 +99,13 @@ func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 3 { m.query.Model.Set("fill", true) m.query.Model.Set("fillInterval", interval.Seconds()) - if args[2] == "NULL" { - m.query.Model.Set("fillNull", true) - } else { + switch args[2] { + case "NULL": + m.query.Model.Set("fillMode", "null") + case "last": + m.query.Model.Set("fillMode", "last") + default: + m.query.Model.Set("fillMode", "value") floatVal, err := strconv.ParseFloat(args[2], 64) if err != nil { return "", fmt.Errorf("error parsing fill value %v", args[2]) diff --git a/pkg/tsdb/mssql/macros_test.go b/pkg/tsdb/mssql/macros_test.go index ea50c418de7..b808666d967 100644 --- a/pkg/tsdb/mssql/macros_test.go +++ b/pkg/tsdb/mssql/macros_test.go @@ -76,12 +76,25 @@ func TestMacroEngine(t *testing.T) { _, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', NULL)") fill := query.Model.Get("fill").MustBool() - fillNull := query.Model.Get("fillNull").MustBool() + fillMode := query.Model.Get("fillMode").MustString() fillInterval := query.Model.Get("fillInterval").MustInt() So(err, ShouldBeNil) So(fill, ShouldBeTrue) - So(fillNull, ShouldBeTrue) + So(fillMode, ShouldEqual, "null") + So(fillInterval, ShouldEqual, 5*time.Minute.Seconds()) + }) + + Convey("interpolate __timeGroup function with fill (value = last)", func() { + _, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', last)") + + fill := query.Model.Get("fill").MustBool() + fillMode := query.Model.Get("fillMode").MustString() + fillInterval := query.Model.Get("fillInterval").MustInt() + + So(err, ShouldBeNil) + So(fill, ShouldBeTrue) + So(fillMode, ShouldEqual, "last") So(fillInterval, ShouldEqual, 5*time.Minute.Seconds()) }) diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go index a56fd1ceb2a..bebf4b396bb 100644 --- a/pkg/tsdb/mysql/macros.go +++ b/pkg/tsdb/mysql/macros.go @@ -94,9 +94,13 @@ func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, er if len(args) == 3 { m.query.Model.Set("fill", true) m.query.Model.Set("fillInterval", interval.Seconds()) - if args[2] == "NULL" { - m.query.Model.Set("fillNull", true) - } else { + switch args[2] { + case "NULL": + m.query.Model.Set("fillMode", "null") + case "last": + m.query.Model.Set("fillMode", "last") + default: + m.query.Model.Set("fillMode", "value") floatVal, err := strconv.ParseFloat(args[2], 64) if err != nil { return "", fmt.Errorf("error parsing fill value %v", args[2]) diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index 9947c23498b..fe262a3f758 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -295,7 +295,7 @@ func TestMySQL(t *testing.T) { }) - Convey("When doing a metric query using timeGroup with float fill enabled", func() { + Convey("When doing a metric query using timeGroup with value fill enabled", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { @@ -320,6 +320,35 @@ func TestMySQL(t *testing.T) { points := queryResult.Series[0].Points So(points[3][0].Float64, ShouldEqual, 1.5) }) + + Convey("When doing a metric query using timeGroup with last fill enabled", func() { + query := &tsdb.TsdbQuery{ + Queries: []*tsdb.Query{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": "SELECT $__timeGroup(time, '5m', last) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1", + "format": "time_series", + }), + RefId: "A", + }, + }, + TimeRange: &tsdb.TimeRange{ + From: fmt.Sprintf("%v", fromStart.Unix()*1000), + To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000), + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + points := queryResult.Series[0].Points + So(points[2][0].Float64, ShouldEqual, 15.0) + So(points[3][0].Float64, ShouldEqual, 15.0) + So(points[6][0].Float64, ShouldEqual, 20.0) + }) + }) Convey("Given a table with metrics having multiple values and measurements", func() { diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index 9e337caf3ec..3ab21ea0c6e 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -116,9 +116,13 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string, if len(args) == 3 { m.query.Model.Set("fill", true) m.query.Model.Set("fillInterval", interval.Seconds()) - if args[2] == "NULL" { - m.query.Model.Set("fillNull", true) - } else { + switch args[2] { + case "NULL": + m.query.Model.Set("fillMode", "null") + case "last": + m.query.Model.Set("fillMode", "last") + default: + m.query.Model.Set("fillMode", "value") floatVal, err := strconv.ParseFloat(args[2], 64) if err != nil { return "", fmt.Errorf("error parsing fill value %v", args[2]) diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index 3e864dca1e6..ac0964e912c 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -276,7 +276,7 @@ func TestPostgres(t *testing.T) { }) - Convey("When doing a metric query using timeGroup with float fill enabled", func() { + Convey("When doing a metric query using timeGroup with value fill enabled", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { @@ -303,6 +303,34 @@ func TestPostgres(t *testing.T) { }) }) + Convey("When doing a metric query using timeGroup with last fill enabled", func() { + query := &tsdb.TsdbQuery{ + Queries: []*tsdb.Query{ + { + Model: simplejson.NewFromAny(map[string]interface{}{ + "rawSql": "SELECT $__timeGroup(time, '5m', last), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1", + "format": "time_series", + }), + RefId: "A", + }, + }, + TimeRange: &tsdb.TimeRange{ + From: fmt.Sprintf("%v", fromStart.Unix()*1000), + To: fmt.Sprintf("%v", fromStart.Add(34*time.Minute).Unix()*1000), + }, + } + + resp, err := endpoint.Query(nil, nil, query) + So(err, ShouldBeNil) + queryResult := resp.Results["A"] + So(queryResult.Error, ShouldBeNil) + + points := queryResult.Series[0].Points + So(points[2][0].Float64, ShouldEqual, 15.0) + So(points[3][0].Float64, ShouldEqual, 15.0) + So(points[6][0].Float64, ShouldEqual, 20.0) + }) + Convey("Given a table with metrics having multiple values and measurements", func() { type metric_values struct { Time time.Time diff --git a/pkg/tsdb/sql_engine.go b/pkg/tsdb/sql_engine.go index 3f681a5cdd7..f2f8b17db5f 100644 --- a/pkg/tsdb/sql_engine.go +++ b/pkg/tsdb/sql_engine.go @@ -274,9 +274,15 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *Query, rows *core.Rows, fillMissing := query.Model.Get("fill").MustBool(false) var fillInterval float64 fillValue := null.Float{} + fillLast := false + if fillMissing { fillInterval = query.Model.Get("fillInterval").MustFloat64() * 1000 - if !query.Model.Get("fillNull").MustBool(false) { + switch query.Model.Get("fillMode").MustString() { + case "null": + case "last": + fillLast = true + case "value": fillValue.Float64 = query.Model.Get("fillValue").MustFloat64() fillValue.Valid = true } @@ -352,6 +358,14 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *Query, rows *core.Rows, intervalStart = series.Points[len(series.Points)-1][1].Float64 + fillInterval } + if fillLast { + if len(series.Points) > 0 { + fillValue = series.Points[len(series.Points)-1][0] + } else { + fillValue.Valid = false + } + } + // align interval start intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval @@ -377,6 +391,14 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *Query, rows *core.Rows, intervalStart := series.Points[len(series.Points)-1][1].Float64 intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) + if fillLast { + if len(series.Points) > 0 { + fillValue = series.Points[len(series.Points)-1][0] + } else { + fillValue.Valid = false + } + } + // align interval start intervalStart = math.Floor(intervalStart/fillInterval) * fillInterval for i := intervalStart + fillInterval; i < intervalEnd; i += fillInterval { diff --git a/public/app/plugins/datasource/mssql/partials/query.editor.html b/public/app/plugins/datasource/mssql/partials/query.editor.html index e1320aabde2..e873d60ebbf 100644 --- a/public/app/plugins/datasource/mssql/partials/query.editor.html +++ b/public/app/plugins/datasource/mssql/partials/query.editor.html @@ -53,7 +53,9 @@ Macros: - $__timeEpoch(column) -> DATEDIFF(second, '1970-01-01', column) AS time - $__timeFilter(column) -> column BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:01:17Z' - $__unixEpochFilter(column) -> column >= 1492750877 AND column <= 1492750877 -- $__timeGroup(column, '5m'[, fillvalue]) -> CAST(ROUND(DATEDIFF(second, '1970-01-01', column)/300.0, 0) as bigint)*300. Providing a fillValue of NULL or floating value will automatically fill empty series in timerange with that value. +- $__timeGroup(column, '5m'[, fillvalue]) -> CAST(ROUND(DATEDIFF(second, '1970-01-01', column)/300.0, 0) as bigint)*300. + by setting fillvalue grafana will fill in missing values according to the interval + fillvalue can be either a literal value, NULL or last; last will fill in the last seen value or NULL if none has been seen yet - $__timeGroupAlias(column, '5m'[, fillvalue]) -> CAST(ROUND(DATEDIFF(second, '1970-01-01', column)/300.0, 0) as bigint)*300 AS [time] Example of group by and order by with $__timeGroup: diff --git a/public/app/plugins/datasource/mysql/partials/query.editor.html b/public/app/plugins/datasource/mysql/partials/query.editor.html index db12a3fe8ce..664481ec8dc 100644 --- a/public/app/plugins/datasource/mysql/partials/query.editor.html +++ b/public/app/plugins/datasource/mysql/partials/query.editor.html @@ -53,7 +53,9 @@ Macros: - $__timeEpoch(column) -> UNIX_TIMESTAMP(column) as time_sec - $__timeFilter(column) -> column BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:01:17Z' - $__unixEpochFilter(column) -> time_unix_epoch > 1492750877 AND time_unix_epoch < 1492750877 -- $__timeGroup(column,'5m') -> cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed) +- $__timeGroup(column,'5m'[, fillvalue]) -> cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed) + by setting fillvalue grafana will fill in missing values according to the interval + fillvalue can be either a literal value, NULL or last; last will fill in the last seen value or NULL if none has been seen yet - $__timeGroupAlias(column,'5m') -> cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed) AS "time" Example of group by and order by with $__timeGroup: diff --git a/public/app/plugins/datasource/postgres/partials/query.editor.html b/public/app/plugins/datasource/postgres/partials/query.editor.html index 1b7278f6809..c455c0ebaf9 100644 --- a/public/app/plugins/datasource/postgres/partials/query.editor.html +++ b/public/app/plugins/datasource/postgres/partials/query.editor.html @@ -53,7 +53,9 @@ Macros: - $__timeEpoch -> extract(epoch from column) as "time" - $__timeFilter(column) -> column BETWEEN '2017-04-21T05:01:17Z' AND '2017-04-21T05:01:17Z' - $__unixEpochFilter(column) -> column >= 1492750877 AND column <= 1492750877 -- $__timeGroup(column,'5m') -> (extract(epoch from column)/300)::bigint*300 +- $__timeGroup(column,'5m'[, fillvalue]) -> (extract(epoch from column)/300)::bigint*300 + by setting fillvalue grafana will fill in missing values according to the interval + fillvalue can be either a literal value, NULL or last; last will fill in the last seen value or NULL if none has been seen yet - $__timeGroupAlias(column,'5m') -> (extract(epoch from column)/300)::bigint*300 AS "time" Example of group by and order by with $__timeGroup: From 83d7ec1da2b9a00a542e955f6a41d4a6dbf75c63 Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Tue, 31 Jul 2018 06:36:45 +0200 Subject: [PATCH 11/62] specify grafana version for last fill mode --- docs/sources/features/datasources/mssql.md | 2 +- docs/sources/features/datasources/mysql.md | 2 +- docs/sources/features/datasources/postgres.md | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/features/datasources/mssql.md b/docs/sources/features/datasources/mssql.md index 524a93a943b..9a149df120d 100644 --- a/docs/sources/features/datasources/mssql.md +++ b/docs/sources/features/datasources/mssql.md @@ -83,7 +83,7 @@ Macro example | Description *$__timeGroup(dateColumn,'5m'[, fillvalue])* | Will be replaced by an expression usable in GROUP BY clause. Providing a *fillValue* of *NULL* or *floating value* will automatically fill empty series in timerange with that value.
For example, *CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)\*300*. *$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value. *$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points. -*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used. +*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used (only available in Grafana 5.3+). *$__timeGroupAlias(dateColumn,'5m')* | Will be replaced identical to $__timeGroup but with an added column alias (only available in Grafana 5.3+). *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* diff --git a/docs/sources/features/datasources/mysql.md b/docs/sources/features/datasources/mysql.md index 153b3d7bbf5..4f4efb6e29a 100644 --- a/docs/sources/features/datasources/mysql.md +++ b/docs/sources/features/datasources/mysql.md @@ -66,7 +66,7 @@ Macro example | Description *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),* *$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value. *$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points. -*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used. +*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used (only available in Grafana 5.3+). *$__timeGroupAlias(dateColumn,'5m')* | Will be replaced identical to $__timeGroup but with an added column alias (only available in Grafana 5.3+). *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* diff --git a/docs/sources/features/datasources/postgres.md b/docs/sources/features/datasources/postgres.md index b776b7cbe58..f2b54d3f0ce 100644 --- a/docs/sources/features/datasources/postgres.md +++ b/docs/sources/features/datasources/postgres.md @@ -64,6 +64,7 @@ Macro example | Description *$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value. *$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points. *$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used. +*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used (only available in Grafana 5.3+). *$__timeGroupAlias(dateColumn,'5m')* | Will be replaced identical to $__timeGroup but with an added column alias (only available in Grafana 5.3+). *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn >= 1494410783 AND dateColumn <= 1494497183* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* From 0ff54d257ade3d5a4fb3369dc2c6509378533a39 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Thu, 2 Aug 2018 17:42:28 +0200 Subject: [PATCH 12/62] build: makes it easier to build a local docker container. --- .gitignore | 1 + Makefile | 8 +++++++- packaging/docker/Dockerfile | 9 +++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 11df66360d9..2484176a469 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ debug.test /examples/*/dist /packaging/**/*.rpm /packaging/**/*.deb +/packaging/**/*.tar.gz # Ignore OSX indexing .DS_Store diff --git a/Makefile b/Makefile index c1d755d247d..9e136688eb7 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,12 @@ build-js: build: build-go build-js +build-docker-dev: + @echo "\033[92mInfo:\033[0m the frontend code is expected to be built already." + go run build.go -goos linux -pkg-arch amd64 ${OPT} build package-only latest + cp dist/grafana-latest.linux-x64.tar.gz packaging/docker + cd packaging/docker && docker build --tag grafana/grafana:dev . + test-go: go test -v ./pkg/... @@ -36,4 +42,4 @@ run: ./bin/grafana-server protoc: - protoc -I pkg/tsdb/models pkg/tsdb/models/*.proto --go_out=plugins=grpc:pkg/tsdb/models/. \ No newline at end of file + protoc -I pkg/tsdb/models pkg/tsdb/models/*.proto --go_out=plugins=grpc:pkg/tsdb/models/. diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index 3025b03f920..aaaf333fc6b 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -12,14 +12,15 @@ ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bi GF_PATHS_PLUGINS="/var/lib/grafana/plugins" \ GF_PATHS_PROVISIONING="/etc/grafana/provisioning" +RUN apt-get update && apt-get install -qq -y tar libfontconfig ca-certificates && \ + apt-get autoremove -y && \ + rm -rf /var/lib/apt/lists/* + COPY ${GRAFANA_TGZ} /tmp/grafana.tar.gz -RUN apt-get update && apt-get install -qq -y tar libfontconfig ca-certificates && \ - mkdir -p "$GF_PATHS_HOME/.aws" && \ +RUN mkdir -p "$GF_PATHS_HOME/.aws" && \ tar xfvz /tmp/grafana.tar.gz --strip-components=1 -C "$GF_PATHS_HOME" && \ rm /tmp/grafana.tar.gz && \ - apt-get autoremove -y && \ - rm -rf /var/lib/apt/lists/* && \ groupadd -r -g $GF_GID grafana && \ useradd -r -u $GF_UID -g grafana grafana && \ mkdir -p "$GF_PATHS_PROVISIONING/datasources" \ From bda49fcaa209f9136659814d6900b3d156c2adca Mon Sep 17 00:00:00 2001 From: David Date: Fri, 3 Aug 2018 10:20:13 +0200 Subject: [PATCH 13/62] Add click on explore table cell to add filter to query (#12729) * Add click on explore table cell to add filter to query - move query state from query row to explore container to be able to set modified queries - added TS interface for columns in table model - plumbing from table cell click to datasource - add modifyQuery to prometheus datasource - implement addFilter as addLabelToQuery with tests * Review feedback - using airbnb style for Cell declaration - fixed addLabelToQuery for complex label values --- public/app/containers/Explore/Explore.tsx | 31 ++++++-- public/app/containers/Explore/QueryRows.tsx | 18 +---- public/app/containers/Explore/Table.tsx | 52 +++++++++++-- public/app/core/table_model.ts | 12 ++- .../datasource/prometheus/datasource.ts | 74 +++++++++++++++++++ .../prometheus/result_transformer.ts | 2 +- .../prometheus/specs/datasource.jest.ts | 27 ++++++- .../specs/result_transformer.jest.ts | 6 +- public/sass/pages/_explore.scss | 4 + 9 files changed, 190 insertions(+), 36 deletions(-) diff --git a/public/app/containers/Explore/Explore.tsx b/public/app/containers/Explore/Explore.tsx index 178e53198d4..a0bb38a13f1 100644 --- a/public/app/containers/Explore/Explore.tsx +++ b/public/app/containers/Explore/Explore.tsx @@ -187,11 +187,14 @@ export class Explore extends React.Component { this.setDatasource(datasource); }; - handleChangeQuery = (query, index) => { + handleChangeQuery = (value, index) => { const { queries } = this.state; + const prevQuery = queries[index]; + const edited = prevQuery.query !== value; const nextQuery = { ...queries[index], - query, + edited, + query: value, }; const nextQueries = [...queries]; nextQueries[index] = nextQuery; @@ -254,6 +257,18 @@ export class Explore extends React.Component { } }; + onClickTableCell = (columnKey: string, rowValue: string) => { + const { datasource, queries } = this.state; + if (datasource && datasource.modifyQuery) { + const nextQueries = queries.map(q => ({ + ...q, + edited: false, + query: datasource.modifyQuery(q.query, { addFilter: { key: columnKey, value: rowValue } }), + })); + this.setState({ queries: nextQueries }, () => this.handleSubmit()); + } + }; + buildQueryOptions(targetOptions: { format: string; instant?: boolean }) { const { datasource, queries, range } = this.state; const resolution = this.el.offsetWidth; @@ -390,12 +405,12 @@ export class Explore extends React.Component { ) : ( -
- -
- )} + + )} {!datasourceMissing ? (
{ {datasource && !datasourceError ? (
{ split={split} /> ) : null} - {supportsTable && showingTable ? : null} + {supportsTable && showingTable ? ( +
+ ) : null} {supportsLogs && showingLogs ? : null} diff --git a/public/app/containers/Explore/PromQueryField.tsx b/public/app/containers/Explore/PromQueryField.tsx index c6119cc9d0f..274f604a7fb 100644 --- a/public/app/containers/Explore/PromQueryField.tsx +++ b/public/app/containers/Explore/PromQueryField.tsx @@ -1,4 +1,5 @@ import _ from 'lodash'; +import moment from 'moment'; import React from 'react'; import { Value } from 'slate'; @@ -19,6 +20,8 @@ import TypeaheadField, { const DEFAULT_KEYS = ['job', 'instance']; const EMPTY_SELECTOR = '{}'; +const HISTORY_ITEM_COUNT = 5; +const HISTORY_COUNT_CUTOFF = 1000 * 60 * 60 * 24; // 24h const METRIC_MARK = 'metric'; const PRISM_LANGUAGE = 'promql'; @@ -28,6 +31,22 @@ export const setFunctionMove = (suggestion: Suggestion): Suggestion => { return suggestion; }; +export function addHistoryMetadata(item: Suggestion, history: any[]): Suggestion { + const cutoffTs = Date.now() - HISTORY_COUNT_CUTOFF; + const historyForItem = history.filter(h => h.ts > cutoffTs && h.query === item.label); + const count = historyForItem.length; + const recent = historyForItem.pop(); + let hint = `Queried ${count} times in the last 24h.`; + if (recent) { + const lastQueried = moment(recent.ts).fromNow(); + hint = `${hint} Last queried ${lastQueried}.`; + } + return { + ...item, + documentation: hint, + }; +} + export function willApplySuggestion( suggestion: string, { typeaheadContext, typeaheadText }: TypeaheadFieldState @@ -59,6 +78,7 @@ export function willApplySuggestion( } interface PromQueryFieldProps { + history?: any[]; initialQuery?: string | null; labelKeys?: { [index: string]: string[] }; // metric -> [labelKey,...] labelValues?: { [index: string]: { [index: string]: string[] } }; // metric -> labelKey -> [labelValue,...] @@ -162,17 +182,38 @@ class PromQueryField extends React.Component 0) { + const historyItems = _.chain(history) + .uniqBy('query') + .takeRight(HISTORY_ITEM_COUNT) + .map(h => h.query) + .map(wrapLabel) + .map(item => addHistoryMetadata(item, history)) + .reverse() + .value(); + + suggestions.push({ + prefixMatch: true, + skipSort: true, + label: 'History', + items: historyItems, + }); + } + suggestions.push({ prefixMatch: true, label: 'Functions', items: FUNCTIONS.map(setFunctionMove), }); - if (this.state.metrics) { + if (metrics) { suggestions.push({ label: 'Metrics', - items: this.state.metrics.map(wrapLabel), + items: metrics.map(wrapLabel), }); } return { suggestions }; diff --git a/public/app/containers/Explore/QueryField.tsx b/public/app/containers/Explore/QueryField.tsx index 238549c1303..e261eb3ca80 100644 --- a/public/app/containers/Explore/QueryField.tsx +++ b/public/app/containers/Explore/QueryField.tsx @@ -97,6 +97,10 @@ export interface SuggestionGroup { * If true, do not filter items in this group based on the search. */ skipFilter?: boolean; + /** + * If true, do not sort items. + */ + skipSort?: boolean; } interface TypeaheadFieldProps { @@ -244,7 +248,9 @@ class QueryField extends React.Component c.insertText || (c.filterText || c.label) !== prefix); } - group.items = _.sortBy(group.items, item => item.sortText || item.label); + if (!group.skipSort) { + group.items = _.sortBy(group.items, item => item.sortText || item.label); + } } return group; }) diff --git a/public/app/containers/Explore/QueryRows.tsx b/public/app/containers/Explore/QueryRows.tsx index d2c1d81607f..bc8972e0660 100644 --- a/public/app/containers/Explore/QueryRows.tsx +++ b/public/app/containers/Explore/QueryRows.tsx @@ -2,7 +2,7 @@ import React, { PureComponent } from 'react'; import QueryField from './PromQueryField'; -class QueryRow extends PureComponent { +class QueryRow extends PureComponent { handleChangeQuery = value => { const { index, onChangeQuery } = this.props; if (onChangeQuery) { @@ -32,7 +32,7 @@ class QueryRow extends PureComponent { }; render() { - const { request, query, edited } = this.props; + const { edited, history, query, request } = this.props; return (
@@ -46,6 +46,7 @@ class QueryRow extends PureComponent {
{ } } -export default class QueryRows extends PureComponent { +export default class QueryRows extends PureComponent { render() { const { className = '', queries, ...handlers } = this.props; return ( diff --git a/public/app/core/specs/store.jest.ts b/public/app/core/specs/store.jest.ts index 0162960621d..ac02501f99e 100644 --- a/public/app/core/specs/store.jest.ts +++ b/public/app/core/specs/store.jest.ts @@ -32,6 +32,18 @@ describe('store', () => { expect(store.getBool('key5', false)).toBe(true); }); + it('gets an object', () => { + expect(store.getObject('object1')).toBeUndefined(); + expect(store.getObject('object1', [])).toEqual([]); + store.setObject('object1', [1]); + expect(store.getObject('object1')).toEqual([1]); + }); + + it('sets an object', () => { + expect(store.setObject('object2', { a: 1 })).toBe(true); + expect(store.getObject('object2')).toEqual({ a: 1 }); + }); + it('key should be deleted', () => { store.set('key6', '123'); store.delete('key6'); diff --git a/public/app/core/store.ts b/public/app/core/store.ts index b0714f49256..7cc969cf97f 100644 --- a/public/app/core/store.ts +++ b/public/app/core/store.ts @@ -14,6 +14,38 @@ export class Store { return window.localStorage[key] === 'true'; } + getObject(key: string, def?: any) { + let ret = def; + if (this.exists(key)) { + const json = window.localStorage[key]; + try { + ret = JSON.parse(json); + } catch (error) { + console.error(`Error parsing store object: ${key}. Returning default: ${def}. [${error}]`); + } + } + return ret; + } + + // Returns true when successfully stored + setObject(key: string, value: any): boolean { + let json; + try { + json = JSON.stringify(value); + } catch (error) { + console.error(`Could not stringify object: ${key}. [${error}]`); + return false; + } + try { + this.set(key, json); + } catch (error) { + // Likely hitting storage quota + console.error(`Could not save item in localStorage: ${key}. [${error}]`); + return false; + } + return true; + } + exists(key) { return window.localStorage[key] !== void 0; } From cda3b01781887e4356eb9b2d8062b8db7c96046c Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Fri, 3 Aug 2018 11:40:44 +0200 Subject: [PATCH 40/62] Reversed history direction for explore - _.reverse() was modifying state.history --- public/app/containers/Explore/Explore.tsx | 4 ++-- public/app/containers/Explore/PromQueryField.tsx | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/public/app/containers/Explore/Explore.tsx b/public/app/containers/Explore/Explore.tsx index e4de96dbdf2..31fd082c94c 100644 --- a/public/app/containers/Explore/Explore.tsx +++ b/public/app/containers/Explore/Explore.tsx @@ -289,10 +289,10 @@ export class Explore extends React.Component { const ts = Date.now(); queries.forEach(q => { const { query } = q; - history = [...history, { query, ts }]; + history = [{ query, ts }, ...history]; }); if (history.length > MAX_HISTORY_ITEMS) { - history = history.slice(history.length - MAX_HISTORY_ITEMS); + history = history.slice(0, MAX_HISTORY_ITEMS); } // Combine all queries of a datasource type into one history const historyKey = `grafana.explore.history.${datasourceId}`; diff --git a/public/app/containers/Explore/PromQueryField.tsx b/public/app/containers/Explore/PromQueryField.tsx index 274f604a7fb..a527589e7b2 100644 --- a/public/app/containers/Explore/PromQueryField.tsx +++ b/public/app/containers/Explore/PromQueryField.tsx @@ -35,7 +35,7 @@ export function addHistoryMetadata(item: Suggestion, history: any[]): Suggestion const cutoffTs = Date.now() - HISTORY_COUNT_CUTOFF; const historyForItem = history.filter(h => h.ts > cutoffTs && h.query === item.label); const count = historyForItem.length; - const recent = historyForItem.pop(); + const recent = historyForItem[0]; let hint = `Queried ${count} times in the last 24h.`; if (recent) { const lastQueried = moment(recent.ts).fromNow(); @@ -189,11 +189,10 @@ class PromQueryField extends React.Component 0) { const historyItems = _.chain(history) .uniqBy('query') - .takeRight(HISTORY_ITEM_COUNT) + .take(HISTORY_ITEM_COUNT) .map(h => h.query) .map(wrapLabel) .map(item => addHistoryMetadata(item, history)) - .reverse() .value(); suggestions.push({ From 0d9870d9f1c283be414726e42523c29595e21f2b Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 6 Aug 2018 16:26:59 +0200 Subject: [PATCH 41/62] build: failing to push to docker hub fails the build. --- packaging/docker/build-deploy.sh | 1 + packaging/docker/push_to_docker_hub.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/packaging/docker/build-deploy.sh b/packaging/docker/build-deploy.sh index e20ae2c2a41..ac3226a4a61 100755 --- a/packaging/docker/build-deploy.sh +++ b/packaging/docker/build-deploy.sh @@ -1,4 +1,5 @@ #!/bin/sh +set -e _grafana_version=$1 ./build.sh "$_grafana_version" diff --git a/packaging/docker/push_to_docker_hub.sh b/packaging/docker/push_to_docker_hub.sh index e779b04d68d..3cf97d580ca 100755 --- a/packaging/docker/push_to_docker_hub.sh +++ b/packaging/docker/push_to_docker_hub.sh @@ -1,4 +1,5 @@ #!/bin/sh +set -e _grafana_tag=$1 From a73fc4a688acdb3e40107f109f0e4d2e33efa5a9 Mon Sep 17 00:00:00 2001 From: Leonard Gram Date: Mon, 6 Aug 2018 17:34:25 +0200 Subject: [PATCH 42/62] Smaller docker image (#12824) * build: makes the grafana docker image smaller. * build: branches and PR:s builds the docker image. --- .circleci/config.yml | 22 ++++++++++++++++++++++ packaging/docker/Dockerfile | 17 +++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e2deab62c1b..8f2e9b6c1af 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -194,6 +194,18 @@ jobs: - run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker - run: cd packaging/docker && ./build-deploy.sh "master-${CIRCLE_SHA1}" + grafana-docker-pr: + docker: + - image: docker:stable-git + steps: + - checkout + - attach_workspace: + at: . + - setup_remote_docker + - run: docker info + - run: cp dist/grafana-latest.linux-x64.tar.gz packaging/docker + - run: cd packaging/docker && ./build.sh "${CIRCLE_SHA1}" + grafana-docker-release: docker: - image: docker:stable-git @@ -387,3 +399,13 @@ workflows: filters: *filter-not-release-or-master - postgres-integration-test: filters: *filter-not-release-or-master + - grafana-docker-pr: + requires: + - build + - test-backend + - test-frontend + - codespell + - gometalinter + - mysql-integration-test + - postgres-integration-test + filters: *filter-not-release-or-master diff --git a/packaging/docker/Dockerfile b/packaging/docker/Dockerfile index aaaf333fc6b..e2109b74909 100644 --- a/packaging/docker/Dockerfile +++ b/packaging/docker/Dockerfile @@ -1,6 +1,17 @@ FROM debian:stretch-slim ARG GRAFANA_TGZ="grafana-latest.linux-x64.tar.gz" + +RUN apt-get update && apt-get install -qq -y tar && \ + apt-get autoremove -y && \ + rm -rf /var/lib/apt/lists/* + +COPY ${GRAFANA_TGZ} /tmp/grafana.tar.gz + +RUN mkdir /tmp/grafana && tar xfvz /tmp/grafana.tar.gz --strip-components=1 -C /tmp/grafana + +FROM debian:stretch-slim + ARG GF_UID="472" ARG GF_GID="472" @@ -12,15 +23,13 @@ ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bi GF_PATHS_PLUGINS="/var/lib/grafana/plugins" \ GF_PATHS_PROVISIONING="/etc/grafana/provisioning" -RUN apt-get update && apt-get install -qq -y tar libfontconfig ca-certificates && \ +RUN apt-get update && apt-get install -qq -y libfontconfig ca-certificates && \ apt-get autoremove -y && \ rm -rf /var/lib/apt/lists/* -COPY ${GRAFANA_TGZ} /tmp/grafana.tar.gz +COPY --from=0 /tmp/grafana "$GF_PATHS_HOME" RUN mkdir -p "$GF_PATHS_HOME/.aws" && \ - tar xfvz /tmp/grafana.tar.gz --strip-components=1 -C "$GF_PATHS_HOME" && \ - rm /tmp/grafana.tar.gz && \ groupadd -r -g $GF_GID grafana && \ useradd -r -u $GF_UID -g grafana grafana && \ mkdir -p "$GF_PATHS_PROVISIONING/datasources" \ From e115e600dbafed9baa5d10d8d42ec062eceee9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 7 Aug 2018 11:35:05 +0200 Subject: [PATCH 43/62] Update ROADMAP.md --- ROADMAP.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ROADMAP.md b/ROADMAP.md index 6f8111fd2d4..002811eded7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,6 +6,7 @@ But it will give you an idea of our current vision and plan. ### Short term (1-2 months) - Multi-Stat panel - Metrics & Log Explore UI + - Backend plugins ### Mid term (2-4 months) - React Panels From 433b0abf6d37c09f42a724f2fdbff9fa7c32d9a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 7 Aug 2018 11:36:17 +0200 Subject: [PATCH 44/62] Update ROADMAP.md --- ROADMAP.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 002811eded7..37d4c723a7d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,12 +6,12 @@ But it will give you an idea of our current vision and plan. ### Short term (1-2 months) - Multi-Stat panel - Metrics & Log Explore UI - - Backend plugins ### Mid term (2-4 months) - React Panels - Change visualization (panel type) on the fly. - Templating Query Editor UI Plugin hook + - Backend plugins ### Long term (4 - 8 months) From 4a387a96552ffd54493c00afd8e7673e90d228d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 7 Aug 2018 11:43:04 +0200 Subject: [PATCH 45/62] Update ROADMAP.md --- ROADMAP.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 37d4c723a7d..891bc9f790b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,9 +1,10 @@ -# Roadmap (2018-06-26) +# Roadmap (2018-08-07) This roadmap is a tentative plan for the core development team. Things change constantly as PRs come in and priorities change. But it will give you an idea of our current vision and plan. ### Short term (1-2 months) + - PRs & Bugs - Multi-Stat panel - Metrics & Log Explore UI @@ -14,15 +15,13 @@ But it will give you an idea of our current vision and plan. - Backend plugins ### Long term (4 - 8 months) - -- Alerting improvements (silence, per series tracking, etc) -- Progress on React migration + - Alerting improvements (silence, per series tracking, etc) + - Progress on React migration ### In a distant future far far away - -- Meta queries -- Integrated light weight TSDB -- Web socket & live data sources + - Meta queries + - Integrated light weight TSDB + - Web socket & live data sources ### Outside contributions We know this is being worked on right now by contributors (and we hope to merge it when it's ready). From 0f94d2f5f1c0aae35566260a4dc5f0711e0466c8 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 7 Aug 2018 12:34:12 +0200 Subject: [PATCH 46/62] Fix closing parens completion for prometheus queries in Explore (#12810) - position was determined by SPACE, but Prometheus selectors can contain spaces - added negative lookahead to check if space is outside a selector - moved braces plugin into PromQueryField since braces are prom specific --- public/app/containers/Explore/PromQueryField.tsx | 2 ++ public/app/containers/Explore/QueryField.tsx | 3 +-- .../app/containers/Explore/slate-plugins/braces.jest.ts | 9 +++++++++ public/app/containers/Explore/slate-plugins/braces.ts | 6 ++++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/public/app/containers/Explore/PromQueryField.tsx b/public/app/containers/Explore/PromQueryField.tsx index a527589e7b2..68f31d8ffd6 100644 --- a/public/app/containers/Explore/PromQueryField.tsx +++ b/public/app/containers/Explore/PromQueryField.tsx @@ -7,6 +7,7 @@ import { Value } from 'slate'; import { getNextCharacter, getPreviousCousin } from './utils/dom'; import PluginPrism, { setPrismTokens } from './slate-plugins/prism/index'; import PrismPromql, { FUNCTIONS } from './slate-plugins/prism/promql'; +import BracesPlugin from './slate-plugins/braces'; import RunnerPlugin from './slate-plugins/runner'; import { processLabels, RATE_RANGES, cleanText, getCleanSelector } from './utils/prometheus'; @@ -110,6 +111,7 @@ class PromQueryField extends React.Component { handler(event, change); expect(Plain.serialize(change.value)).toEqual('(foo) (bar)() ugh'); }); + + it('adds closing braces outside a selector', () => { + const change = Plain.deserialize('sumrate(metric{namespace="dev", cluster="c1"}[2m])').change(); + let event; + change.move(3); + event = new window.KeyboardEvent('keydown', { key: '(' }); + handler(event, change); + expect(Plain.serialize(change.value)).toEqual('sum(rate(metric{namespace="dev", cluster="c1"}[2m]))'); + }); }); diff --git a/public/app/containers/Explore/slate-plugins/braces.ts b/public/app/containers/Explore/slate-plugins/braces.ts index b92a224d111..2ea58569ef0 100644 --- a/public/app/containers/Explore/slate-plugins/braces.ts +++ b/public/app/containers/Explore/slate-plugins/braces.ts @@ -4,6 +4,8 @@ const BRACES = { '(': ')', }; +const NON_SELECTOR_SPACE_REGEXP = / (?![^}]+})/; + export default function BracesPlugin() { return { onKeyDown(event, change) { @@ -28,8 +30,8 @@ export default function BracesPlugin() { event.preventDefault(); const text = value.anchorText.text; const offset = value.anchorOffset; - const space = text.indexOf(' ', offset); - const length = space > 0 ? space : text.length; + const delimiterIndex = text.slice(offset).search(NON_SELECTOR_SPACE_REGEXP); + const length = delimiterIndex > -1 ? delimiterIndex + offset : text.length; const forward = length - offset; // Insert matching braces change From f1c1633d154ce643321876419af29672e5d283ca Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Sat, 4 Aug 2018 11:07:48 +0200 Subject: [PATCH 47/62] Explore: show message if queries did not return data - every result viewer displays a message that it received an empty data set --- public/app/containers/Explore/Explore.tsx | 19 ++++++++++--------- public/app/containers/Explore/Graph.tsx | 9 ++++++++- public/app/containers/Explore/Logs.tsx | 1 + public/app/containers/Explore/Table.tsx | 21 +++++++++++++++++++-- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/public/app/containers/Explore/Explore.tsx b/public/app/containers/Explore/Explore.tsx index 31fd082c94c..53c43782ad6 100644 --- a/public/app/containers/Explore/Explore.tsx +++ b/public/app/containers/Explore/Explore.tsx @@ -440,12 +440,12 @@ export class Explore extends React.Component {
) : ( -
- -
- )} +
+ )} {!datasourceMissing ? (
+
) : null} - {supportsLogs && showingLogs ? : null} + {supportsLogs && showingLogs ? : null} ) : null} diff --git a/public/app/containers/Explore/Graph.tsx b/public/app/containers/Explore/Graph.tsx index a43ddfb2aa5..eeda29b1292 100644 --- a/public/app/containers/Explore/Graph.tsx +++ b/public/app/containers/Explore/Graph.tsx @@ -123,7 +123,14 @@ class Graph extends Component { } render() { - const { data, height } = this.props; + const { data, height, loading } = this.props; + if (!loading && data && data.length === 0) { + return ( +
+
The queries returned no time series to graph.
+
+ ); + } return (
diff --git a/public/app/containers/Explore/Logs.tsx b/public/app/containers/Explore/Logs.tsx index 10d7827a9a3..ae2d5e2daa6 100644 --- a/public/app/containers/Explore/Logs.tsx +++ b/public/app/containers/Explore/Logs.tsx @@ -5,6 +5,7 @@ import { LogsModel, LogRow } from 'app/core/logs_model'; interface LogsProps { className?: string; data: LogsModel; + loading: boolean; } const EXAMPLE_QUERY = '{job="default/prometheus"}'; diff --git a/public/app/containers/Explore/Table.tsx b/public/app/containers/Explore/Table.tsx index 0856acd5d89..5cf41563704 100644 --- a/public/app/containers/Explore/Table.tsx +++ b/public/app/containers/Explore/Table.tsx @@ -6,6 +6,7 @@ const EMPTY_TABLE = new TableModel(); interface TableProps { className?: string; data: TableModel; + loading: boolean; onClickCell?: (columnKey: string, rowValue: string) => void; } @@ -38,8 +39,24 @@ function Cell(props: SFCCellProps) { export default class Table extends PureComponent { render() { - const { className = '', data, onClickCell } = this.props; - const tableModel = data || EMPTY_TABLE; + const { className = '', data, loading, onClickCell } = this.props; + let tableModel = data || EMPTY_TABLE; + if (!loading && data && data.rows.length === 0) { + return ( +
+ + + + + + + + + + +
Table
The queries returned no data for a table.
+ ); + } return ( From 00f04f4ea0d0eab8ab1cc724b5431675e98d8d91 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Sat, 4 Aug 2018 11:47:04 +0200 Subject: [PATCH 48/62] Add clear button to Explore - Clear All button to clear all queries and results - moved result viewer buttons below query rows to make it more clear that they govern result options --- public/app/containers/Explore/Explore.tsx | 50 +++++++++++++++-------- public/app/containers/Explore/Graph.tsx | 3 +- public/sass/pages/_explore.scss | 8 ++++ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/public/app/containers/Explore/Explore.tsx b/public/app/containers/Explore/Explore.tsx index 53c43782ad6..772617dd7c1 100644 --- a/public/app/containers/Explore/Explore.tsx +++ b/public/app/containers/Explore/Explore.tsx @@ -267,6 +267,15 @@ export class Explore extends React.Component { } }; + onClickClear = () => { + this.setState({ + graphResult: null, + logsResult: null, + queries: ensureQueries(), + tableResult: null, + }); + }; + onClickTableCell = (columnKey: string, rowValue: string) => { const { datasource, queries } = this.state; if (datasource && datasource.modifyQuery) { @@ -466,24 +475,12 @@ export class Explore extends React.Component { ) : null} -
- {supportsGraph ? ( - - ) : null} - {supportsTable ? ( - - ) : null} - {supportsLogs ? ( - - ) : null} -
+
+ +
+ ) : null} + {supportsTable ? ( + + ) : null} + {supportsLogs ? ( + + ) : null} +
+
{supportsGraph && showingGraph ? ( { draw() { const { data, options: userOptions } = this.props; + const $el = $(`#${this.props.id}`); if (!data) { + $el.empty(); return; } const series = data.map((ts: TimeSeries) => ({ @@ -93,7 +95,6 @@ class Graph extends Component { data: ts.getFlotPairs('null'), })); - const $el = $(`#${this.props.id}`); const ticks = $el.width() / 100; let { from, to } = userOptions.range; if (!moment.isMoment(from)) { diff --git a/public/sass/pages/_explore.scss b/public/sass/pages/_explore.scss index 59b8b62f349..52ddbc03636 100644 --- a/public/sass/pages/_explore.scss +++ b/public/sass/pages/_explore.scss @@ -47,6 +47,14 @@ background-color: $btn-active-bg; } + .navbar-button--no-icon { + line-height: 18px; + } + + .result-options { + margin-top: 2 * $panel-margin; + } + .elapsed-time { position: absolute; left: 0; From 307248f713d00b889325b353ec9ba47f1c87f914 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Sat, 4 Aug 2018 11:58:54 +0200 Subject: [PATCH 49/62] Add clear row button - clears the content of a query row --- public/app/containers/Explore/Explore.tsx | 136 ++++++++++---------- public/app/containers/Explore/QueryRows.tsx | 26 ++-- public/sass/pages/_explore.scss | 2 +- 3 files changed, 87 insertions(+), 77 deletions(-) diff --git a/public/app/containers/Explore/Explore.tsx b/public/app/containers/Explore/Explore.tsx index 772617dd7c1..b21a78ed8ab 100644 --- a/public/app/containers/Explore/Explore.tsx +++ b/public/app/containers/Explore/Explore.tsx @@ -166,7 +166,7 @@ export class Explore extends React.Component { supportsTable, datasourceLoading: false, }, - () => datasourceError === null && this.handleSubmit() + () => datasourceError === null && this.onSubmit() ); } @@ -174,7 +174,7 @@ export class Explore extends React.Component { this.el = el; }; - handleAddQueryRow = index => { + onAddQueryRow = index => { const { queries } = this.state; const nextQueries = [ ...queries.slice(0, index + 1), @@ -184,7 +184,7 @@ export class Explore extends React.Component { this.setState({ queries: nextQueries }); }; - handleChangeDatasource = async option => { + onChangeDatasource = async option => { this.setState({ datasource: null, datasourceError: null, @@ -197,10 +197,10 @@ export class Explore extends React.Component { this.setDatasource(datasource); }; - handleChangeQuery = (value, index) => { + onChangeQuery = (value: string, index: number, override?: boolean) => { const { queries } = this.state; const prevQuery = queries[index]; - const edited = prevQuery.query !== value; + const edited = override ? false : prevQuery.query !== value; const nextQuery = { ...queries[index], edited, @@ -211,60 +211,12 @@ export class Explore extends React.Component { this.setState({ queries: nextQueries }); }; - handleChangeTime = nextRange => { + onChangeTime = nextRange => { const range = { from: nextRange.from, to: nextRange.to, }; - this.setState({ range }, () => this.handleSubmit()); - }; - - handleClickCloseSplit = () => { - const { onChangeSplit } = this.props; - if (onChangeSplit) { - onChangeSplit(false); - } - }; - - handleClickGraphButton = () => { - this.setState(state => ({ showingGraph: !state.showingGraph })); - }; - - handleClickLogsButton = () => { - this.setState(state => ({ showingLogs: !state.showingLogs })); - }; - - handleClickSplit = () => { - const { onChangeSplit } = this.props; - if (onChangeSplit) { - onChangeSplit(true, this.state); - } - }; - - handleClickTableButton = () => { - this.setState(state => ({ showingTable: !state.showingTable })); - }; - - handleRemoveQueryRow = index => { - const { queries } = this.state; - if (queries.length <= 1) { - return; - } - const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)]; - this.setState({ queries: nextQueries }, () => this.handleSubmit()); - }; - - handleSubmit = () => { - const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state; - if (showingTable && supportsTable) { - this.runTableQuery(); - } - if (showingGraph && supportsGraph) { - this.runGraphQuery(); - } - if (showingLogs && supportsLogs) { - this.runLogsQuery(); - } + this.setState({ range }, () => this.onSubmit()); }; onClickClear = () => { @@ -276,6 +228,32 @@ export class Explore extends React.Component { }); }; + onClickCloseSplit = () => { + const { onChangeSplit } = this.props; + if (onChangeSplit) { + onChangeSplit(false); + } + }; + + onClickGraphButton = () => { + this.setState(state => ({ showingGraph: !state.showingGraph })); + }; + + onClickLogsButton = () => { + this.setState(state => ({ showingLogs: !state.showingLogs })); + }; + + onClickSplit = () => { + const { onChangeSplit } = this.props; + if (onChangeSplit) { + onChangeSplit(true, this.state); + } + }; + + onClickTableButton = () => { + this.setState(state => ({ showingTable: !state.showingTable })); + }; + onClickTableCell = (columnKey: string, rowValue: string) => { const { datasource, queries } = this.state; if (datasource && datasource.modifyQuery) { @@ -284,7 +262,29 @@ export class Explore extends React.Component { edited: false, query: datasource.modifyQuery(q.query, { addFilter: { key: columnKey, value: rowValue } }), })); - this.setState({ queries: nextQueries }, () => this.handleSubmit()); + this.setState({ queries: nextQueries }, () => this.onSubmit()); + } + }; + + onRemoveQueryRow = index => { + const { queries } = this.state; + if (queries.length <= 1) { + return; + } + const nextQueries = [...queries.slice(0, index), ...queries.slice(index + 1)]; + this.setState({ queries: nextQueries }, () => this.onSubmit()); + }; + + onSubmit = () => { + const { showingLogs, showingGraph, showingTable, supportsGraph, supportsLogs, supportsTable } = this.state; + if (showingTable && supportsTable) { + this.runTableQuery(); + } + if (showingGraph && supportsGraph) { + this.runGraphQuery(); + } + if (showingLogs && supportsLogs) { + this.runLogsQuery(); } }; @@ -450,7 +450,7 @@ export class Explore extends React.Component { ) : (
-
@@ -460,7 +460,7 @@ export class Explore extends React.Component {
{row.map((value, j) => ( - + ))} ))} diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index ec7103cba95..be3a3b90f78 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -1,7 +1,7 @@ // vendor @import '../vendor/css/timepicker.css'; @import '../vendor/css/spectrum.css'; -@import '../vendor/css/rc-cascader.css'; +@import '../vendor/css/rc-cascader.scss'; // MIXINS @import 'mixins/mixins'; diff --git a/public/vendor/css/rc-cascader.css b/public/vendor/css/rc-cascader.scss similarity index 88% rename from public/vendor/css/rc-cascader.css rename to public/vendor/css/rc-cascader.scss index 968c1fc770f..5cfaaf4961a 100644 --- a/public/vendor/css/rc-cascader.css +++ b/public/vendor/css/rc-cascader.scss @@ -4,11 +4,11 @@ .rc-cascader-menus { font-size: 12px; overflow: hidden; - background: #fff; + background: $panel-bg; position: absolute; - border: 1px solid #d9d9d9; - border-radius: 6px; - box-shadow: 0 0 4px rgba(0, 0, 0, 0.17); + border: $panel-border; + border-radius: $border-radius; + box-shadow: $typeahead-shadow; white-space: nowrap; } .rc-cascader-menus-hidden { @@ -57,7 +57,7 @@ list-style: none; margin: 0; padding: 0; - border-right: 1px solid #e9e9e9; + border-right: $panel-border; overflow: auto; } .rc-cascader-menu:last-child { @@ -75,11 +75,11 @@ position: relative; } .rc-cascader-menu-item:hover { - background: #eaf8fe; + background: $typeahead-selected-bg; } .rc-cascader-menu-item-disabled { cursor: not-allowed; - color: #ccc; + color: $text-color-weak; } .rc-cascader-menu-item-disabled:hover { background: transparent; @@ -88,14 +88,16 @@ position: absolute; right: 12px; content: 'loading'; - color: #aaa; + color: $text-color-weak; font-style: italic; } .rc-cascader-menu-item-active { - background: #d5f1fd; + color: $typeahead-selected-color; + background: $typeahead-selected-bg; } .rc-cascader-menu-item-active:hover { - background: #d5f1fd; + color: $typeahead-selected-color; + background: $typeahead-selected-bg; } .rc-cascader-menu-item-expand { position: relative; @@ -103,7 +105,7 @@ .rc-cascader-menu-item-expand:after { content: '>'; font-size: 12px; - color: #999; + color: $text-color-weak; position: absolute; right: 16px; line-height: 32px; From eb1b9405b2f8b410ff28479abe4192de365b9a79 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 7 Aug 2018 17:56:02 +0200 Subject: [PATCH 55/62] return proper payload from api when updating datasource --- pkg/api/datasources.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 6ffefea991a..23dbb221d71 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -158,12 +158,26 @@ func UpdateDataSource(c *m.ReqContext, cmd m.UpdateDataSourceCommand) Response { } return Error(500, "Failed to update datasource", err) } - ds := convertModelToDtos(cmd.Result) + + query := m.GetDataSourceByIdQuery{ + Id: cmd.Id, + OrgId: c.OrgId, + } + + if err := bus.Dispatch(&query); err != nil { + if err == m.ErrDataSourceNotFound { + return Error(404, "Data source not found", nil) + } + return Error(500, "Failed to query datasources", err) + } + + dtos := convertModelToDtos(query.Result) + return JSON(200, util.DynMap{ "message": "Datasource updated", "id": cmd.Id, "name": cmd.Name, - "datasource": ds, + "datasource": dtos, }) } From ee7602ec1fd8e1303dc12a3c7f6fc105228e2893 Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Tue, 7 Aug 2018 21:01:41 +0200 Subject: [PATCH 56/62] change fillmode from last to previous --- docs/sources/features/datasources/mssql.md | 2 +- docs/sources/features/datasources/mysql.md | 2 +- docs/sources/features/datasources/postgres.md | 3 +-- pkg/tsdb/mssql/macros.go | 4 ++-- pkg/tsdb/mssql/macros_test.go | 6 +++--- pkg/tsdb/mysql/macros.go | 4 ++-- pkg/tsdb/mysql/mysql_test.go | 4 ++-- pkg/tsdb/postgres/macros.go | 4 ++-- pkg/tsdb/postgres/postgres_test.go | 4 ++-- pkg/tsdb/sql_engine.go | 10 +++++----- 10 files changed, 21 insertions(+), 22 deletions(-) diff --git a/docs/sources/features/datasources/mssql.md b/docs/sources/features/datasources/mssql.md index 9a149df120d..caaf5a6b321 100644 --- a/docs/sources/features/datasources/mssql.md +++ b/docs/sources/features/datasources/mssql.md @@ -83,7 +83,7 @@ Macro example | Description *$__timeGroup(dateColumn,'5m'[, fillvalue])* | Will be replaced by an expression usable in GROUP BY clause. Providing a *fillValue* of *NULL* or *floating value* will automatically fill empty series in timerange with that value.
For example, *CAST(ROUND(DATEDIFF(second, '1970-01-01', time_column)/300.0, 0) as bigint)\*300*. *$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value. *$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points. -*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used (only available in Grafana 5.3+). +*$__timeGroup(dateColumn,'5m', previous)* | Same as above but the previous value in that series will be used as fill value if no value has been seen yet NULL will be used (only available in Grafana 5.3+). *$__timeGroupAlias(dateColumn,'5m')* | Will be replaced identical to $__timeGroup but with an added column alias (only available in Grafana 5.3+). *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* diff --git a/docs/sources/features/datasources/mysql.md b/docs/sources/features/datasources/mysql.md index 4f4efb6e29a..cdb78deed35 100644 --- a/docs/sources/features/datasources/mysql.md +++ b/docs/sources/features/datasources/mysql.md @@ -66,7 +66,7 @@ Macro example | Description *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *cast(cast(UNIX_TIMESTAMP(dateColumn)/(300) as signed)*300 as signed),* *$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value. *$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points. -*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used (only available in Grafana 5.3+). +*$__timeGroup(dateColumn,'5m', previous)* | Same as above but the previous value in that series will be used as fill value if no value has been seen yet NULL will be used (only available in Grafana 5.3+). *$__timeGroupAlias(dateColumn,'5m')* | Will be replaced identical to $__timeGroup but with an added column alias (only available in Grafana 5.3+). *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn > 1494410783 AND dateColumn < 1494497183* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* diff --git a/docs/sources/features/datasources/postgres.md b/docs/sources/features/datasources/postgres.md index f2b54d3f0ce..2be2db0837b 100644 --- a/docs/sources/features/datasources/postgres.md +++ b/docs/sources/features/datasources/postgres.md @@ -63,8 +63,7 @@ Macro example | Description *$__timeGroup(dateColumn,'5m')* | Will be replaced by an expression usable in GROUP BY clause. For example, *(extract(epoch from dateColumn)/300)::bigint*300* *$__timeGroup(dateColumn,'5m', 0)* | Same as above but with a fill parameter so missing points in that series will be added by grafana and 0 will be used as value. *$__timeGroup(dateColumn,'5m', NULL)* | Same as above but NULL will be used as value for missing points. -*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used. -*$__timeGroup(dateColumn,'5m', last)* | Same as above but the last seen value in that series will be used as fill value if no value has been seen yet NULL will be used (only available in Grafana 5.3+). +*$__timeGroup(dateColumn,'5m', previous)* | Same as above but the previous value in that series will be used as fill value if no value has been seen yet NULL will be used (only available in Grafana 5.3+). *$__timeGroupAlias(dateColumn,'5m')* | Will be replaced identical to $__timeGroup but with an added column alias (only available in Grafana 5.3+). *$__unixEpochFilter(dateColumn)* | Will be replaced by a time range filter using the specified column name with times represented as unix timestamp. For example, *dateColumn >= 1494410783 AND dateColumn <= 1494497183* *$__unixEpochFrom()* | Will be replaced by the start of the currently active time selection as unix timestamp. For example, *1494410783* diff --git a/pkg/tsdb/mssql/macros.go b/pkg/tsdb/mssql/macros.go index 57a37d618e0..42e47ce6d3c 100644 --- a/pkg/tsdb/mssql/macros.go +++ b/pkg/tsdb/mssql/macros.go @@ -102,8 +102,8 @@ func (m *msSqlMacroEngine) evaluateMacro(name string, args []string) (string, er switch args[2] { case "NULL": m.query.Model.Set("fillMode", "null") - case "last": - m.query.Model.Set("fillMode", "last") + case "previous": + m.query.Model.Set("fillMode", "previous") default: m.query.Model.Set("fillMode", "value") floatVal, err := strconv.ParseFloat(args[2], 64) diff --git a/pkg/tsdb/mssql/macros_test.go b/pkg/tsdb/mssql/macros_test.go index b808666d967..8362ae05aa6 100644 --- a/pkg/tsdb/mssql/macros_test.go +++ b/pkg/tsdb/mssql/macros_test.go @@ -85,8 +85,8 @@ func TestMacroEngine(t *testing.T) { So(fillInterval, ShouldEqual, 5*time.Minute.Seconds()) }) - Convey("interpolate __timeGroup function with fill (value = last)", func() { - _, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', last)") + Convey("interpolate __timeGroup function with fill (value = previous)", func() { + _, err := engine.Interpolate(query, timeRange, "GROUP BY $__timeGroup(time_column,'5m', previous)") fill := query.Model.Get("fill").MustBool() fillMode := query.Model.Get("fillMode").MustString() @@ -94,7 +94,7 @@ func TestMacroEngine(t *testing.T) { So(err, ShouldBeNil) So(fill, ShouldBeTrue) - So(fillMode, ShouldEqual, "last") + So(fillMode, ShouldEqual, "previous") So(fillInterval, ShouldEqual, 5*time.Minute.Seconds()) }) diff --git a/pkg/tsdb/mysql/macros.go b/pkg/tsdb/mysql/macros.go index bebf4b396bb..905d424f29a 100644 --- a/pkg/tsdb/mysql/macros.go +++ b/pkg/tsdb/mysql/macros.go @@ -97,8 +97,8 @@ func (m *mySqlMacroEngine) evaluateMacro(name string, args []string) (string, er switch args[2] { case "NULL": m.query.Model.Set("fillMode", "null") - case "last": - m.query.Model.Set("fillMode", "last") + case "previous": + m.query.Model.Set("fillMode", "previous") default: m.query.Model.Set("fillMode", "value") floatVal, err := strconv.ParseFloat(args[2], 64) diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go index fe262a3f758..ca6df8e360e 100644 --- a/pkg/tsdb/mysql/mysql_test.go +++ b/pkg/tsdb/mysql/mysql_test.go @@ -321,12 +321,12 @@ func TestMySQL(t *testing.T) { So(points[3][0].Float64, ShouldEqual, 1.5) }) - Convey("When doing a metric query using timeGroup with last fill enabled", func() { + Convey("When doing a metric query using timeGroup with previous fill enabled", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": "SELECT $__timeGroup(time, '5m', last) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1", + "rawSql": "SELECT $__timeGroup(time, '5m', previous) as time_sec, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1", "format": "time_series", }), RefId: "A", diff --git a/pkg/tsdb/postgres/macros.go b/pkg/tsdb/postgres/macros.go index 3ab21ea0c6e..aebdc55d1d7 100644 --- a/pkg/tsdb/postgres/macros.go +++ b/pkg/tsdb/postgres/macros.go @@ -119,8 +119,8 @@ func (m *postgresMacroEngine) evaluateMacro(name string, args []string) (string, switch args[2] { case "NULL": m.query.Model.Set("fillMode", "null") - case "last": - m.query.Model.Set("fillMode", "last") + case "previous": + m.query.Model.Set("fillMode", "previous") default: m.query.Model.Set("fillMode", "value") floatVal, err := strconv.ParseFloat(args[2], 64) diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go index ac0964e912c..9e363529df1 100644 --- a/pkg/tsdb/postgres/postgres_test.go +++ b/pkg/tsdb/postgres/postgres_test.go @@ -303,12 +303,12 @@ func TestPostgres(t *testing.T) { }) }) - Convey("When doing a metric query using timeGroup with last fill enabled", func() { + Convey("When doing a metric query using timeGroup with previous fill enabled", func() { query := &tsdb.TsdbQuery{ Queries: []*tsdb.Query{ { Model: simplejson.NewFromAny(map[string]interface{}{ - "rawSql": "SELECT $__timeGroup(time, '5m', last), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1", + "rawSql": "SELECT $__timeGroup(time, '5m', previous), avg(value) as value FROM metric GROUP BY 1 ORDER BY 1", "format": "time_series", }), RefId: "A", diff --git a/pkg/tsdb/sql_engine.go b/pkg/tsdb/sql_engine.go index f2f8b17db5f..cbf6d6b4d60 100644 --- a/pkg/tsdb/sql_engine.go +++ b/pkg/tsdb/sql_engine.go @@ -274,14 +274,14 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *Query, rows *core.Rows, fillMissing := query.Model.Get("fill").MustBool(false) var fillInterval float64 fillValue := null.Float{} - fillLast := false + fillPrevious := false if fillMissing { fillInterval = query.Model.Get("fillInterval").MustFloat64() * 1000 switch query.Model.Get("fillMode").MustString() { case "null": - case "last": - fillLast = true + case "previous": + fillPrevious = true case "value": fillValue.Float64 = query.Model.Get("fillValue").MustFloat64() fillValue.Valid = true @@ -358,7 +358,7 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *Query, rows *core.Rows, intervalStart = series.Points[len(series.Points)-1][1].Float64 + fillInterval } - if fillLast { + if fillPrevious { if len(series.Points) > 0 { fillValue = series.Points[len(series.Points)-1][0] } else { @@ -391,7 +391,7 @@ func (e *sqlQueryEndpoint) transformToTimeSeries(query *Query, rows *core.Rows, intervalStart := series.Points[len(series.Points)-1][1].Float64 intervalEnd := float64(tsdbQuery.TimeRange.MustGetTo().UnixNano() / 1e6) - if fillLast { + if fillPrevious { if len(series.Points) > 0 { fillValue = series.Points[len(series.Points)-1][0] } else { From 52c7edf2f41e4c3479b39e401b4e1778c461f581 Mon Sep 17 00:00:00 2001 From: Sven Klemm Date: Tue, 7 Aug 2018 21:11:51 +0200 Subject: [PATCH 57/62] rename last fillmode to previous --- public/app/plugins/datasource/mssql/partials/query.editor.html | 2 +- public/app/plugins/datasource/mysql/partials/query.editor.html | 2 +- .../app/plugins/datasource/postgres/partials/query.editor.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/app/plugins/datasource/mssql/partials/query.editor.html b/public/app/plugins/datasource/mssql/partials/query.editor.html index e873d60ebbf..7888e36a24c 100644 --- a/public/app/plugins/datasource/mssql/partials/query.editor.html +++ b/public/app/plugins/datasource/mssql/partials/query.editor.html @@ -55,7 +55,7 @@ Macros: - $__unixEpochFilter(column) -> column >= 1492750877 AND column <= 1492750877 - $__timeGroup(column, '5m'[, fillvalue]) -> CAST(ROUND(DATEDIFF(second, '1970-01-01', column)/300.0, 0) as bigint)*300. by setting fillvalue grafana will fill in missing values according to the interval - fillvalue can be either a literal value, NULL or last; last will fill in the last seen value or NULL if none has been seen yet + fillvalue can be either a literal value, NULL or previous; previous will fill in the previous seen value or NULL if none has been seen yet - $__timeGroupAlias(column, '5m'[, fillvalue]) -> CAST(ROUND(DATEDIFF(second, '1970-01-01', column)/300.0, 0) as bigint)*300 AS [time] Example of group by and order by with $__timeGroup: diff --git a/public/app/plugins/datasource/mysql/partials/query.editor.html b/public/app/plugins/datasource/mysql/partials/query.editor.html index 664481ec8dc..7c799eec21b 100644 --- a/public/app/plugins/datasource/mysql/partials/query.editor.html +++ b/public/app/plugins/datasource/mysql/partials/query.editor.html @@ -55,7 +55,7 @@ Macros: - $__unixEpochFilter(column) -> time_unix_epoch > 1492750877 AND time_unix_epoch < 1492750877 - $__timeGroup(column,'5m'[, fillvalue]) -> cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed) by setting fillvalue grafana will fill in missing values according to the interval - fillvalue can be either a literal value, NULL or last; last will fill in the last seen value or NULL if none has been seen yet + fillvalue can be either a literal value, NULL or previous; previous will fill in the previous seen value or NULL if none has been seen yet - $__timeGroupAlias(column,'5m') -> cast(cast(UNIX_TIMESTAMP(column)/(300) as signed)*300 as signed) AS "time" Example of group by and order by with $__timeGroup: diff --git a/public/app/plugins/datasource/postgres/partials/query.editor.html b/public/app/plugins/datasource/postgres/partials/query.editor.html index c455c0ebaf9..20353b81ba2 100644 --- a/public/app/plugins/datasource/postgres/partials/query.editor.html +++ b/public/app/plugins/datasource/postgres/partials/query.editor.html @@ -55,7 +55,7 @@ Macros: - $__unixEpochFilter(column) -> column >= 1492750877 AND column <= 1492750877 - $__timeGroup(column,'5m'[, fillvalue]) -> (extract(epoch from column)/300)::bigint*300 by setting fillvalue grafana will fill in missing values according to the interval - fillvalue can be either a literal value, NULL or last; last will fill in the last seen value or NULL if none has been seen yet + fillvalue can be either a literal value, NULL or previous; previous will fill in the previous seen value or NULL if none has been seen yet - $__timeGroupAlias(column,'5m') -> (extract(epoch from column)/300)::bigint*300 AS "time" Example of group by and order by with $__timeGroup: From a156b6ee06a4b0610430afb254c23242154f1452 Mon Sep 17 00:00:00 2001 From: Ben de Luca Date: Tue, 7 Aug 2018 22:32:02 +0200 Subject: [PATCH 58/62] fix missing * The missing * causes the text to be in the box to be displayed incorrectly. --- docs/sources/features/datasources/elasticsearch.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/features/datasources/elasticsearch.md b/docs/sources/features/datasources/elasticsearch.md index 31ce78f0bfe..d29327cf480 100644 --- a/docs/sources/features/datasources/elasticsearch.md +++ b/docs/sources/features/datasources/elasticsearch.md @@ -115,7 +115,7 @@ The Elasticsearch data source supports two types of queries you can use in the * Query | Description ------------ | ------------- -*{"find": "fields", "type": "keyword"} | Returns a list of field names with the index type `keyword`. +*{"find": "fields", "type": "keyword"}* | Returns a list of field names with the index type `keyword`. *{"find": "terms", "field": "@hostname", "size": 1000}* | Returns a list of values for a field using term aggregation. Query will user current dashboard time range as time range for query. *{"find": "terms", "field": "@hostname", "query": ''}* | Returns a list of values for a field using term aggregation & and a specified lucene query filter. Query will use current dashboard time range as time range for query. From e8dfbe94b1e1d6832dfb3acd11dae8b01a8fa6d3 Mon Sep 17 00:00:00 2001 From: tariq1890 Date: Sun, 5 Aug 2018 13:54:06 -0700 Subject: [PATCH 59/62] Fixing bug in url query reader and added test cases --- pkg/util/url.go | 2 +- pkg/util/url_test.go | 27 +++++++++++++++++++++++++++ pkg/util/validation_test.go | 22 ++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 pkg/util/validation_test.go diff --git a/pkg/util/url.go b/pkg/util/url.go index c82dcef67c5..fad2d79a6d0 100644 --- a/pkg/util/url.go +++ b/pkg/util/url.go @@ -10,7 +10,7 @@ type UrlQueryReader struct { } func NewUrlQueryReader(urlInfo *url.URL) (*UrlQueryReader, error) { - u, err := url.ParseQuery(urlInfo.String()) + u, err := url.ParseQuery(urlInfo.RawQuery) if err != nil { return nil, err } diff --git a/pkg/util/url_test.go b/pkg/util/url_test.go index 4dd221b9e0b..ee29956f60d 100644 --- a/pkg/util/url_test.go +++ b/pkg/util/url_test.go @@ -4,6 +4,7 @@ import ( "testing" . "github.com/smartystreets/goconvey/convey" + "net/url" ) func TestUrl(t *testing.T) { @@ -43,4 +44,30 @@ func TestUrl(t *testing.T) { So(result, ShouldEqual, "http://localhost:8080/api/") }) + + Convey("When joining two urls where lefthand side has a trailing slash and righthand side has preceding slash", t, func() { + result := JoinUrlFragments("http://localhost:8080/", "/api/") + + So(result, ShouldEqual, "http://localhost:8080/api/") + }) +} + +func TestNewUrlQueryReader(t *testing.T) { + u, _ := url.Parse("http://www.abc.com/foo?bar=baz&bar2=baz2") + uqr, _ := NewUrlQueryReader(u) + + Convey("when trying to retrieve the first query value", t, func() { + result := uqr.Get("bar", "foodef") + So(result, ShouldEqual, "baz") + }) + + Convey("when trying to retrieve the second query value", t, func() { + result := uqr.Get("bar2", "foodef") + So(result, ShouldEqual, "baz2") + }) + + Convey("when trying to retrieve from a non-existent key, the default value is returned", t, func() { + result := uqr.Get("bar3", "foodef") + So(result, ShouldEqual, "foodef") + }) } diff --git a/pkg/util/validation_test.go b/pkg/util/validation_test.go new file mode 100644 index 00000000000..124da1b744b --- /dev/null +++ b/pkg/util/validation_test.go @@ -0,0 +1,22 @@ +package util + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestIsEmail(t *testing.T) { + + Convey("When validating a string that is a valid email", t, func() { + result := IsEmail("abc@def.com") + + So(result, ShouldEqual, true) + }) + + Convey("When validating a string that is not a valid email", t, func() { + result := IsEmail("abcdef.com") + + So(result, ShouldEqual, false) + }) +} From a6a29f0b2071619ee9a64029542cc27a6b125367 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 8 Aug 2018 09:13:44 +0200 Subject: [PATCH 60/62] changelog: add notes about closing #11270 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9d0670c717..4fa417be5f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ * **Units**: Change units to include characters for power of 2 and 3 [#12744](https://github.com/grafana/grafana/pull/12744), thx [@Worty](https://github.com/Worty) * **Graph**: Option to hide series from tooltip [#3341](https://github.com/grafana/grafana/issues/3341), thx [@mtanda](https://github.com/mtanda) * **UI**: Fix iOS home screen "app" icon and Windows 10 app experience [#12752](https://github.com/grafana/grafana/issues/12752), thx [@andig](https://github.com/andig) +* **Datasource**: Fix UI issue with secret fields after updating datasource [#11270](https://github.com/grafana/grafana/issues/11270) ### Breaking changes From 9938835dde3be364b549e4ace3eea1c044256f2d Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 8 Aug 2018 09:47:45 +0200 Subject: [PATCH 61/62] devenv: update sql dashboards --- .../datasource_tests_mssql_unittest.json | 244 +++++++++++++++--- .../datasource_tests_mysql_unittest.json | 240 ++++++++++++++--- .../datasource_tests_postgres_unittest.json | 243 ++++++++++++++--- 3 files changed, 612 insertions(+), 115 deletions(-) diff --git a/devenv/dev-dashboards/datasource_tests_mssql_unittest.json b/devenv/dev-dashboards/datasource_tests_mssql_unittest.json index 80d3e1a5889..0d291f01a09 100644 --- a/devenv/dev-dashboards/datasource_tests_mssql_unittest.json +++ b/devenv/dev-dashboards/datasource_tests_mssql_unittest.json @@ -64,7 +64,7 @@ "editable": true, "gnetId": null, "graphTooltip": 0, - "iteration": 1532949769359, + "iteration": 1533713720618, "links": [], "panels": [ { @@ -338,8 +338,8 @@ "datasource": "gdev-mssql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, + "h": 6, + "w": 6, "x": 0, "y": 7 }, @@ -421,9 +421,9 @@ "datasource": "gdev-mssql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 8, + "h": 6, + "w": 6, + "x": 6, "y": 7 }, "id": 9, @@ -504,9 +504,9 @@ "datasource": "gdev-mssql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 16, + "h": 6, + "w": 6, + "x": 12, "y": 7 }, "id": 10, @@ -579,6 +579,89 @@ "alignLevel": null } }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-mssql-ds-tests", + "fill": 2, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 7 + }, + "id": 36, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "targets": [ + { + "alias": "", + "format": "time_series", + "rawSql": "SELECT $__timeGroupAlias(time, '5m', previous), avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY $__timeGroup(time, '5m') ORDER BY 1", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "timeGroup macro 5m with fill(previous) and null as zero", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "aliasColors": {}, "bars": true, @@ -587,10 +670,10 @@ "datasource": "gdev-mssql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, + "h": 6, + "w": 6, "x": 0, - "y": 16 + "y": 13 }, "id": 16, "legend": { @@ -670,10 +753,10 @@ "datasource": "gdev-mssql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 8, - "y": 16 + "h": 6, + "w": 6, + "x": 6, + "y": 13 }, "id": 12, "legend": { @@ -753,10 +836,10 @@ "datasource": "gdev-mssql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 16, - "y": 16 + "h": 6, + "w": 6, + "x": 12, + "y": 13 }, "id": 13, "legend": { @@ -828,6 +911,89 @@ "alignLevel": null } }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-mssql-ds-tests", + "fill": 2, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 13 + }, + "id": 37, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "targets": [ + { + "alias": "", + "format": "time_series", + "rawSql": "SELECT $__timeGroupAlias(time, '$summarize', previous), sum(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY $__timeGroup(time, '$summarize') ORDER BY 1", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Metrics - timeGroup macro $summarize with fill(previous)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "aliasColors": {}, "bars": false, @@ -839,7 +1005,7 @@ "h": 8, "w": 12, "x": 0, - "y": 25 + "y": 19 }, "id": 27, "legend": { @@ -926,7 +1092,7 @@ "h": 8, "w": 12, "x": 12, - "y": 25 + "y": 19 }, "id": 5, "legend": { @@ -1029,7 +1195,7 @@ "h": 8, "w": 12, "x": 0, - "y": 33 + "y": 27 }, "id": 4, "legend": { @@ -1116,7 +1282,7 @@ "h": 8, "w": 12, "x": 12, - "y": 33 + "y": 27 }, "id": 28, "legend": { @@ -1201,7 +1367,7 @@ "h": 8, "w": 12, "x": 0, - "y": 41 + "y": 35 }, "id": 19, "legend": { @@ -1288,7 +1454,7 @@ "h": 8, "w": 12, "x": 12, - "y": 41 + "y": 35 }, "id": 18, "legend": { @@ -1373,7 +1539,7 @@ "h": 8, "w": 12, "x": 0, - "y": 49 + "y": 43 }, "id": 17, "legend": { @@ -1460,7 +1626,7 @@ "h": 8, "w": 12, "x": 12, - "y": 49 + "y": 43 }, "id": 20, "legend": { @@ -1545,7 +1711,7 @@ "h": 8, "w": 12, "x": 0, - "y": 57 + "y": 51 }, "id": 29, "legend": { @@ -1632,7 +1798,7 @@ "h": 8, "w": 12, "x": 12, - "y": 57 + "y": 51 }, "id": 30, "legend": { @@ -1719,7 +1885,7 @@ "h": 8, "w": 12, "x": 0, - "y": 65 + "y": 59 }, "id": 14, "legend": { @@ -1807,7 +1973,7 @@ "h": 8, "w": 12, "x": 12, - "y": 65 + "y": 59 }, "id": 15, "legend": { @@ -1894,7 +2060,7 @@ "h": 8, "w": 12, "x": 0, - "y": 73 + "y": 67 }, "id": 25, "legend": { @@ -1982,7 +2148,7 @@ "h": 8, "w": 12, "x": 12, - "y": 73 + "y": 67 }, "id": 22, "legend": { @@ -2069,7 +2235,7 @@ "h": 8, "w": 12, "x": 0, - "y": 81 + "y": 75 }, "id": 21, "legend": { @@ -2157,7 +2323,7 @@ "h": 8, "w": 12, "x": 12, - "y": 81 + "y": 75 }, "id": 26, "legend": { @@ -2244,7 +2410,7 @@ "h": 8, "w": 12, "x": 0, - "y": 89 + "y": 83 }, "id": 23, "legend": { @@ -2332,7 +2498,7 @@ "h": 8, "w": 12, "x": 12, - "y": 89 + "y": 83 }, "id": 24, "legend": { @@ -2542,5 +2708,5 @@ "timezone": "", "title": "Datasource tests - MSSQL (unit test)", "uid": "GlAqcPgmz", - "version": 3 + "version": 10 } \ No newline at end of file diff --git a/devenv/dev-dashboards/datasource_tests_mysql_unittest.json b/devenv/dev-dashboards/datasource_tests_mysql_unittest.json index f684186084a..cec8ebe9d02 100644 --- a/devenv/dev-dashboards/datasource_tests_mysql_unittest.json +++ b/devenv/dev-dashboards/datasource_tests_mysql_unittest.json @@ -64,7 +64,7 @@ "editable": true, "gnetId": null, "graphTooltip": 0, - "iteration": 1532949531280, + "iteration": 1533714324007, "links": [], "panels": [ { @@ -338,8 +338,8 @@ "datasource": "gdev-mysql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, + "h": 6, + "w": 6, "x": 0, "y": 7 }, @@ -421,9 +421,9 @@ "datasource": "gdev-mysql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 8, + "h": 6, + "w": 6, + "x": 6, "y": 7 }, "id": 9, @@ -504,9 +504,9 @@ "datasource": "gdev-mysql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 16, + "h": 6, + "w": 6, + "x": 12, "y": 7 }, "id": 10, @@ -579,6 +579,89 @@ "alignLevel": null } }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-mysql-ds-tests", + "fill": 2, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 7 + }, + "id": 36, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "targets": [ + { + "alias": "", + "format": "time_series", + "rawSql": "SELECT $__timeGroupAlias(time, '5m', previous), avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "timeGroup macro 5m with fill(previous)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "aliasColors": {}, "bars": true, @@ -587,10 +670,10 @@ "datasource": "gdev-mysql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, + "h": 6, + "w": 6, "x": 0, - "y": 16 + "y": 13 }, "id": 16, "legend": { @@ -670,10 +753,10 @@ "datasource": "gdev-mysql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 8, - "y": 16 + "h": 6, + "w": 6, + "x": 6, + "y": 13 }, "id": 12, "legend": { @@ -753,10 +836,10 @@ "datasource": "gdev-mysql-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 16, - "y": 16 + "h": 6, + "w": 6, + "x": 12, + "y": 13 }, "id": 13, "legend": { @@ -828,6 +911,89 @@ "alignLevel": null } }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-mysql-ds-tests", + "fill": 2, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 13 + }, + "id": 37, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "targets": [ + { + "alias": "", + "format": "time_series", + "rawSql": "SELECT $__timeGroupAlias(time, '$summarize', previous), sum(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Metrics - timeGroup macro $summarize with fill(previous)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "aliasColors": {}, "bars": false, @@ -839,7 +1005,7 @@ "h": 8, "w": 12, "x": 0, - "y": 25 + "y": 19 }, "id": 27, "legend": { @@ -926,7 +1092,7 @@ "h": 8, "w": 12, "x": 12, - "y": 25 + "y": 19 }, "id": 5, "legend": { @@ -1023,7 +1189,7 @@ "h": 8, "w": 12, "x": 0, - "y": 33 + "y": 27 }, "id": 4, "legend": { @@ -1110,7 +1276,7 @@ "h": 8, "w": 12, "x": 12, - "y": 33 + "y": 27 }, "id": 28, "legend": { @@ -1195,7 +1361,7 @@ "h": 8, "w": 12, "x": 0, - "y": 41 + "y": 35 }, "id": 19, "legend": { @@ -1282,7 +1448,7 @@ "h": 8, "w": 12, "x": 12, - "y": 41 + "y": 35 }, "id": 18, "legend": { @@ -1367,7 +1533,7 @@ "h": 8, "w": 12, "x": 0, - "y": 49 + "y": 43 }, "id": 17, "legend": { @@ -1454,7 +1620,7 @@ "h": 8, "w": 12, "x": 12, - "y": 49 + "y": 43 }, "id": 20, "legend": { @@ -1539,7 +1705,7 @@ "h": 8, "w": 12, "x": 0, - "y": 57 + "y": 51 }, "id": 14, "legend": { @@ -1627,7 +1793,7 @@ "h": 8, "w": 12, "x": 12, - "y": 57 + "y": 51 }, "id": 15, "legend": { @@ -1714,7 +1880,7 @@ "h": 8, "w": 12, "x": 0, - "y": 65 + "y": 59 }, "id": 25, "legend": { @@ -1802,7 +1968,7 @@ "h": 8, "w": 12, "x": 12, - "y": 65 + "y": 59 }, "id": 22, "legend": { @@ -1889,7 +2055,7 @@ "h": 8, "w": 12, "x": 0, - "y": 73 + "y": 67 }, "id": 21, "legend": { @@ -1977,7 +2143,7 @@ "h": 8, "w": 12, "x": 12, - "y": 73 + "y": 67 }, "id": 26, "legend": { @@ -2064,7 +2230,7 @@ "h": 8, "w": 12, "x": 0, - "y": 81 + "y": 75 }, "id": 23, "legend": { @@ -2152,7 +2318,7 @@ "h": 8, "w": 12, "x": 12, - "y": 81 + "y": 75 }, "id": 24, "legend": { @@ -2360,5 +2526,5 @@ "timezone": "", "title": "Datasource tests - MySQL (unittest)", "uid": "Hmf8FDkmz", - "version": 1 + "version": 9 } \ No newline at end of file diff --git a/devenv/dev-dashboards/datasource_tests_postgres_unittest.json b/devenv/dev-dashboards/datasource_tests_postgres_unittest.json index 3c2b34df78c..cc93308e116 100644 --- a/devenv/dev-dashboards/datasource_tests_postgres_unittest.json +++ b/devenv/dev-dashboards/datasource_tests_postgres_unittest.json @@ -64,7 +64,7 @@ "editable": true, "gnetId": null, "graphTooltip": 0, - "iteration": 1532951521836, + "iteration": 1533714184500, "links": [], "panels": [ { @@ -338,8 +338,8 @@ "datasource": "gdev-postgres-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, + "h": 6, + "w": 6, "x": 0, "y": 7 }, @@ -421,9 +421,9 @@ "datasource": "gdev-postgres-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 8, + "h": 6, + "w": 6, + "x": 6, "y": 7 }, "id": 9, @@ -504,9 +504,9 @@ "datasource": "gdev-postgres-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 16, + "h": 6, + "w": 6, + "x": 12, "y": 7 }, "id": 10, @@ -579,6 +579,89 @@ "alignLevel": null } }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-postgres-ds-tests", + "fill": 2, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 7 + }, + "id": 36, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": true, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "targets": [ + { + "alias": "", + "format": "time_series", + "rawSql": "SELECT $__timeGroupAlias(time, '5m', previous), avg(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "timeGroup macro 5m with fill(previous)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "aliasColors": {}, "bars": true, @@ -587,10 +670,10 @@ "datasource": "gdev-postgres-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, + "h": 6, + "w": 6, "x": 0, - "y": 16 + "y": 13 }, "id": 16, "legend": { @@ -670,10 +753,10 @@ "datasource": "gdev-postgres-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 8, - "y": 16 + "h": 6, + "w": 6, + "x": 6, + "y": 13 }, "id": 12, "legend": { @@ -753,10 +836,10 @@ "datasource": "gdev-postgres-ds-tests", "fill": 2, "gridPos": { - "h": 9, - "w": 8, - "x": 16, - "y": 16 + "h": 6, + "w": 6, + "x": 12, + "y": 13 }, "id": 13, "legend": { @@ -828,6 +911,89 @@ "alignLevel": null } }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": "gdev-postgres-ds-tests", + "fill": 2, + "gridPos": { + "h": 6, + "w": 6, + "x": 18, + "y": 13 + }, + "id": 37, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 2, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 3, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": true, + "targets": [ + { + "alias": "", + "format": "time_series", + "rawSql": "SELECT $__timeGroupAlias(time, '$summarize', previous), sum(value) as value FROM metric WHERE $__timeFilter(time) GROUP BY 1 ORDER BY 1", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Metrics - timeGroup macro $summarize with fill(previous)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "aliasColors": {}, "bars": false, @@ -839,7 +1005,7 @@ "h": 8, "w": 12, "x": 0, - "y": 25 + "y": 19 }, "id": 27, "legend": { @@ -926,7 +1092,7 @@ "h": 8, "w": 12, "x": 12, - "y": 25 + "y": 19 }, "id": 5, "legend": { @@ -1011,7 +1177,7 @@ "h": 8, "w": 12, "x": 0, - "y": 33 + "y": 27 }, "id": 4, "legend": { @@ -1098,7 +1264,7 @@ "h": 8, "w": 12, "x": 12, - "y": 33 + "y": 27 }, "id": 28, "legend": { @@ -1183,7 +1349,7 @@ "h": 8, "w": 12, "x": 0, - "y": 41 + "y": 35 }, "id": 19, "legend": { @@ -1270,7 +1436,7 @@ "h": 8, "w": 12, "x": 12, - "y": 41 + "y": 35 }, "id": 18, "legend": { @@ -1355,7 +1521,7 @@ "h": 8, "w": 12, "x": 0, - "y": 49 + "y": 43 }, "id": 17, "legend": { @@ -1442,7 +1608,7 @@ "h": 8, "w": 12, "x": 12, - "y": 49 + "y": 43 }, "id": 20, "legend": { @@ -1527,7 +1693,7 @@ "h": 8, "w": 12, "x": 0, - "y": 57 + "y": 51 }, "id": 14, "legend": { @@ -1615,7 +1781,7 @@ "h": 8, "w": 12, "x": 12, - "y": 57 + "y": 51 }, "id": 15, "legend": { @@ -1702,7 +1868,7 @@ "h": 8, "w": 12, "x": 0, - "y": 65 + "y": 59 }, "id": 25, "legend": { @@ -1790,7 +1956,7 @@ "h": 8, "w": 12, "x": 12, - "y": 65 + "y": 59 }, "id": 22, "legend": { @@ -1877,7 +2043,7 @@ "h": 8, "w": 12, "x": 0, - "y": 73 + "y": 67 }, "id": 21, "legend": { @@ -1965,7 +2131,7 @@ "h": 8, "w": 12, "x": 12, - "y": 73 + "y": 67 }, "id": 26, "legend": { @@ -2052,7 +2218,7 @@ "h": 8, "w": 12, "x": 0, - "y": 81 + "y": 75 }, "id": 23, "legend": { @@ -2140,7 +2306,7 @@ "h": 8, "w": 12, "x": 12, - "y": 81 + "y": 75 }, "id": 24, "legend": { @@ -2352,6 +2518,5 @@ "timezone": "", "title": "Datasource tests - Postgres (unittest)", "uid": "vHQdlVziz", - "version": 1 -} - + "version": 9 +} \ No newline at end of file From 817179c09733fb4d94ab44fea1d28e7152dafadc Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 8 Aug 2018 10:33:30 +0200 Subject: [PATCH 62/62] changelog: add notes about closing #12756 [skip ci] --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fa417be5f6..4983dbafdcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * **Prometheus**: Heatmap - fix unhandled error when some points are missing [#12484](https://github.com/grafana/grafana/issues/12484) * **Prometheus**: Add $interval, $interval_ms, $range, and $range_ms support for dashboard and template queries [#12597](https://github.com/grafana/grafana/issues/12597) * **Variables**: Skip unneeded extra query request when de-selecting variable values used for repeated panels [#8186](https://github.com/grafana/grafana/issues/8186), thx [@mtanda](https://github.com/mtanda) +* **Postgres/MySQL/MSSQL**: Add previous fill mode to $__timeGroup macro which will fill in previously seen value when point is missing [#12756](https://github.com/grafana/grafana/issues/12756), thx [@svenklemm](https://github.com/svenklemm) * **Postgres/MySQL/MSSQL**: Use floor rounding in $__timeGroup macro function [#12460](https://github.com/grafana/grafana/issues/12460), thx [@svenklemm](https://github.com/svenklemm) * **Postgres/MySQL/MSSQL**: Use metric column as prefix when returning multiple value columns [#12727](https://github.com/grafana/grafana/issues/12727), thx [@svenklemm](https://github.com/svenklemm) * **Postgres/MySQL/MSSQL**: New $__timeGroupAlias macro. Postgres $__timeGroup no longer automatically adds time column alias [#12749](https://github.com/grafana/grafana/issues/12749), thx [@svenklemm](https://github.com/svenklemm)