Redashのクエリエディターのテーマを変える

この記事はRedash Advent Calendar 2017の記事ではありません。

qiita.com

9日目の記事は take4_k さんの「RedashのAzure Table StorageのQuery Runnerを作った 」です。この記事を読む前にぜひ読んでください。

take4.hatenablog.com

やりたいこと

f:id:ariarijp:20171209001542p:plain

この記事では、こんな感じにクエリエディターのテーマを好みのものに変えてみます。

環境構築

おなじみの以下のドキュメントにそって開発環境を構築してある前提です。

Docker Based Developer Installation Guide · Redash Help Center

JSを書き換える

Redashのクエリエディターは Brace というNPMモジュールで作られています。

github.com

Braceの中で使用していると思われる Ace Editor がテーマ切り替えをサポートしているので、その機能を使ってテーマを切り替えます。

Ace - The High Performance Code Editor for the Web

テーマをデフォルトのものから Tommorow Night Bright に変えるため、 client/app/components/queries/query-editor.js を書き換えます。

diff --git a/client/app/components/queries/query-editor.js b/client/app/components/queries/query-editor.js
index 445be461..29bf5494 100644
--- a/client/app/components/queries/query-editor.js
+++ b/client/app/components/queries/query-editor.js
@@ -3,6 +3,8 @@ import 'brace/mode/python';
 import 'brace/mode/sql';
 import 'brace/mode/json';
 import 'brace/ext/language_tools';
+import 'brace/theme/tomorrow_night_bright';
+
 import { map } from 'underscore';

 // By default Ace will try to load snippet files for the different modes and fail.
@@ -46,6 +48,8 @@ function queryEditor(QuerySnippet) {
             editor.commands.bindKey('Cmd+L', null);
             editor.commands.bindKey('Ctrl+L', null);

+            editor.setTheme('ace/theme/tomorrow_night_bright');
+
             QuerySnippet.query((snippets) => {
               window.ace.acequire(['ace/snippets'], (snippetsModule) => {
                 const snippetManager = snippetsModule.snippetManager;

変更できたらビルドします。

$ npm run build

動作確認

docker-compose でRedashを起動し、管理ユーザーやデータソースを作成した後にクエリ作成画面を表示すると、この記事の冒頭の画像のようにテーマが Tomorrow Night Brightになっています。

他のテーマを使用したい場合は以下を参考にしてみてください。

github.com

まとめ

実用上はもう少し工夫して実装した方が良さそうですが、今後Redashの設定ファイルでテーマが切り替えられたりすると面白いかもしれませんね。

Redashで独自のQuery Runnerを作る

この記事はRedash Advent Calendar 2017 8日目の記事です。

qiita.com

独自のQuery Runnerを作る

いきなり元ネタを出しますが、Redashの開発者Arikさんによる、以下の投稿を見るとだいたい作り方の雰囲気がわかってきます。

discuss.redash.io

上記の記事を参考に、クエリの代わりに文字列を貼り付けると、その文字列をCSVのようにパースして結果を返すような、ちょっと変わったQuery Runnerを作成します。

開発環境

細かい開発環境構築方法の説明は割愛しますので、ご了承ください。

以下の公式ドキュメントに開発環境の構築手順が記載されているため、この手順に沿って、Docker上の開発環境が整っていることを前提に進めます。

Docker Based Developer Installation Guide · Redash Help Center

なお、使用しているRedashのバージョンは master ブランチの以下のコミット時点のもので、v4.x系に該当するものだと思います。

github.com

サンプルコード

以下のスクリプトredash/query_runner/csv_parser.py として保存します。

import csv as csv
import json

from redash.query_runner import BaseQueryRunner, register


class CsvParser(BaseQueryRunner):
    @classmethod
    def configuration_schema(cls):
        return {
            'type': 'object',
            'properties': {
                'delimiter': {
                    'type': 'string',
                    'title': 'Delimiter'
                }
            }
        }

    @classmethod
    def annotate_query(cls):
        return False

    def __init__(self, configuration):
        super(CsvParser, self).__init__(configuration)

    def test_connection(self):
        pass

    def run_query(self, query, user):
        data = {
            'columns': [],
            'rows': [],
        }

        delimiter = str(self.configuration.get('delimiter'))

        for row in csv.DictReader(query.strip().splitlines(), delimiter=delimiter):
            if len(data['columns']) == 0:
                for key in row.keys():
                    data['columns'].append({'name': key, 'friendly_name': key})

            data['rows'].append(row)

        return json.dumps(data), None


register(CsvParser)

コードの詳細は割愛しますが、クエリ文字列をCSVとしてパースし、Redashのクエリ結果の形式に準拠した形に整形します。

CSVの1行目はヘッダーとして扱うようにしています。

区切り文字はデータソースの設定として定義できるようになっており、デフォルトは,(半角カンマ)としています。

本来はカラムには string などの型を明示することも可能ですが、このスクリプトでは省略しています。

docker-compose.ymlの変更

独自のQuery Runnerを使用するため、 docker-compose.yml を編集します。

以下は docker-compose.yml の差分になります。

diff --git a/docker-compose.yml b/docker-compose.yml
index 536dd446..2f948d90 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -17,6 +17,7 @@ services:
       REDASH_LOG_LEVEL: "INFO"
       REDASH_REDIS_URL: "redis://redis:6379/0"
       REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
+      REDASH_ADDITIONAL_QUERY_RUNNERS: "redash.query_runner.csv_parser"
   worker:
     build: .
     command: scheduler
@@ -31,6 +32,7 @@ services:
       REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
       QUEUES: "queries,scheduled_queries,celery"
       WORKERS_COUNT: 2
+      REDASH_ADDITIONAL_QUERY_RUNNERS: "redash.query_runner.csv_parser"
   redis:
     image: redis:3.0-alpine
     restart: always

環境変数 REDASH_ADDITIONAL_QUERY_RUNNERS で追加のQuery Runnerを指定できます。環境変数についてはAdvent Calendar 2日目の記事がとても参考になるのでおすすめです。

qiita.com

ここまでで独自Query Runnerを使うための準備は完了です。

動作確認

早速、 docker-compose up して動作確認します。

データベースの作成や管理ユーザの作成については済んでいるものとして進めます。

データソースの作成

データソースの作成画面でTypeのプルダウンをクリックすると、以下のように CsvParser が追加されています。

f:id:ariarijp:20171207195907p:plain

CsvParser を選択すると、以下のようなフォームが表示されます。

f:id:ariarijp:20171207200120p:plain

データソース名は CSV として、区切り文字はデフォルトで , となっているので、あえて :(半角コロン)にしてデータソースを保存します。

クエリの実行

クエリの作成画面に移動し、データソースとして CSV を選択ます。

クエリには以下のような半角コロン区切りの文字列を入力します。

name:ring_name:finishing_move:born_on
Kanji Inoki:Antonio Inoki:Enzuigiri:1943-02-20
Baba Shohei:Giant Baba:Big boot:1938-01-23

テストデータの内容については特に触れず、実行してみます。

f:id:ariarijp:20171207200851p:plain

ここまでの手順に問題がなければ、上のように文字列をパースしてクエリ結果として表示することができます。

カラムの並び順が入力したものと違っているのは、PythonのDictが挿入順を保持しないからでしょうか。Redashは現時点でPython2.7を使用していますが、Python3.6あたりで順序が保持されるようになるので、Python3対応されたら少し結果が変わると思います。

1日目の記事で私が紹介した Query Results データソースで別の結果と結合することもできたりして面白いかもしれませんね。

ariarijp.hatenablog.com

まとめ

この記事ではあまり実用性を考えずに独自のQuery Runnerを作成しましたが、Redashをチームや事業にフィットするようにカスタマイズしたいと考えられる場合、Query Runnerを作るというのは選択肢にいれてみてはいかがでしょうか。

明日は take4_k さんの「azure table storageのquery runner作ってみたので書きます」です。この記事がいい前振りになることを願っています。

RedashのScriptデータソースで独自のデータソースを作成する

この記事は Redash Advent Calendar 2017 5日目の記事です。

qiita.com

昨日はhideji2さんの 「RedashのPythonDataSourceを使ってデータのヘルスチェックをしてみた話」でした。

qiita.com

Scriptデータソースとは

RedashにはScriptデータソースという、任意のスクリプトをデータソースとして使える機能が含まれています。

この記事では、Scriptデータソースを使って自由にデータソースを定義する方法を紹介します。

Dockerで環境構築

Dockerを使用して環境構築をします。

以下のYAMLdocker-compose.yml として保存してください。

version: '2'
services:
  server:
    image: redash/redash:latest
    command: server
    depends_on:
      - postgres
      - redis
    ports:
      - "5000:5000"
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: "INFO"
      REDASH_REDIS_URL: "redis://redis:6379/0"
      REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
      REDASH_COOKIE_SECRET: veryverysecret
      REDASH_WEB_WORKERS: 4
      REDASH_ADDITIONAL_QUERY_RUNNERS: "redash.query_runner.script"
    restart: always
  worker:
    image: redash/redash:latest
    command: scheduler
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: "INFO"
      REDASH_REDIS_URL: "redis://redis:6379/0"
      REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
      QUEUES: "queries,scheduled_queries,celery"
      WORKERS_COUNT: 2
      REDASH_ADDITIONAL_QUERY_RUNNERS: "redash.query_runner.script"
    restart: always
  redis:
    image: redis:3.0-alpine
    restart: always
  postgres:
    image: postgres:9.5.6-alpine
    restart: always
  nginx:
    image: redash/nginx:latest
    ports:
      - "80:80"
    depends_on:
      - server
    links:
      - server:redash
    restart: always

REDASH_ADDITIONAL_QUERY_RUNNERS という環境変数を使用してScriptデータソースを使えるようにします。

環境変数については2日目の記事に詳しく書かれていますので、興味がある方はぜひチェックしてください。

qiita.com

docker-compose.yml が準備できたら、Redashのデータベースを初期化するため、以下のコマンドを実行します。

$ docker-compose run --rm server bash -c "sleep 15 && /app/bin/docker-entrypoint create_db"
$ docker-compose up

Redashが起動したらhttp://localhostにアクセスし、admin ユーザーを作成します。

サンプルスクリプト

以下のスクリプトwrestlers.sh として保存します。

#!/bin/bash
JSON=$(cat << EOS
{
  "columns": [
    {"friendly_name": "Name", "type": "string", "name": "name"},
    {"friendly_name": "Ring name", "type": "string", "name": "ring_name"},
    {"friendly_name": "finishing moves", "type": "string", "name": "finishing_move"},
    {"friendly_name": "Born on", "type": "string", "name": "born_on"}
  ],
  "rows": [
    {"name": "Kanji Inoki", "ring_name": "Antonio Inoki", "finishing_move":"Enzuigiri", "born_on": "1943-02-20"},
    {"name": "Baba Shōhei", "ring_name": "Giant Baba", "finishing_move":"Big boot", "born_on": "1938-01-23"}
  ]
}
EOS
)
/bin/echo $JSON

ファイル名やデータの内容については割愛しますが、RedashのAPIなどで使用されているフォーマットに従ったJSONを返すことが必須となっています。

このフォーマットについては公式ドキュメントにも記載があります。

Data Source Results Format · Redash Help Center

wrestlers.sh を保存したら、以下のコマンドでスクリプトをRedashのWorkerコンテナにコピーし、実行権限を付与します。

$ docker-compose exec worker mkdir /tmp/redash
$ docker cp wrestlers.sh `docker-compose ps | grep worker | awk '{print $1}'`:/tmp/redash/
$ docker-compose exec -u root worker chmod a+x /tmp/redash/wrestlers.sh

データソースの設定

続いて、以下のコマンドでデータソースを追加します。

$ docker-compose exec server ./manage.py ds new --type insecure_script --options '{"path": "*", "shell": true}' wrestlers

path には * を指定していますが、本来はスクリプトの実行を許可するパスを明示的に /tmp/redash のように指定することができるようですが、現時点ではパスを指定すると動作しなくなってしまうため、この記事では * と指定しています。

ここまでで環境構築は完了です。

データソース名から、安全ではない(Insecure)機能だということもわかりました。

本番での使用を検討する際には、任意のスクリプトを実行できるという意味で非常に危険な機能ではありますが、この記事ではScriptデータソースの紹介が主目的なので、先に進みます。

Scriptデータソースを使用したクエリを実行する

動作確認に使用するクエリをRedashの画面上で作成します。

新規クエリをデータソースとして wrestlers を選択し、以下の内容で作成してください。

 /tmp/redash/wrestlers.sh

実行すると、以下のような結果が表示されます。

f:id:ariarijp:20171204213541p:plain

指定したスクリプトの標準出力をクエリの結果として取得できることがわかりました。

この記事の例ではbashで書いていますが、PHPでもGoでもRubyでも、出力がRedashのフォーマットに準じたものであれば、どんな言語で書いても良いというのは、強みになることがあるかもしれません。

まとめ

簡単にまとめます。

  • Scriptデータソースを使用することで、任意のスクリプト言語などでデータソースを独自に定義することができる
  • Scriptデータソースの利用時はセキュリティリスクを含むため、使用を検討する場合は権限などを正しく設定するなどの注意が必要

本番導入へのセキュリティリスクもあり、使い方が難しい機能ですが、うまく使いこなせるとRedashをより快適に使えるようになるかもしれません。

明日はtkmotekiさんが何か書いてくれるようです。気になりますね。

宣伝

Redash Advent Calender 2017を作成した id:kakku22 と一緒にRedash Meetupを開催します。

connpass.com

初回は初学者向けのハンズオンとしていますが、次回以降は導入事例などの情報共有もできるように続けていければと考えていますので、興味を持っていただけたら Twitter あたりでお声がけいただけると嬉しいです。

Redash v3.0.0で追加されるQuery Resultsデータソースについて

この記事は Redash Advent Calendar 2017 1日目の記事です。

qiita.com

Query Resultsデータソースとは

v3.0.0 で追加されるデータソースで、文字通り「クエリの実行結果」をデータソースとして扱うことができるものです。

有料版ではすでに存在していたものがv.3.0.0でOSSになったようで、有料版での使用例は、過去にGunosyさんの記事でも紹介されています。

data.gunosy.io

この記事では、Query Resultsデータソースの利用例を環境構築からクエリ実行までの手順とあわせて紹介します。

環境構築

Redashの環境構築はDockerを利用することが推奨されているので、Dockerを使って環境構築をしますが、Query Resultsデータソースの動作を確認するため、Redash標準のセットアップ手順に加えて、MySQLPostgreSQLSQLiteそれぞれのデータソースを使えるようにします。

サンプルデータとして、MySQLのSakila、それをPostgreSQL向けにしたPagila、さらにSQLite向けのsqlite-sakilaを使用します。

この3つの異なるデータソースのQuery Resultsデータソースを使って扱えるようにしていきましょう。

検証に使用する docker-compose.yml は以下のようなものになります。詳細は割愛しますので、ご了承ください。

version: '2'
services:
  server:
    image: redash/redash:latest
    command: server
    depends_on:
      - postgres
      - redis
    ports:
      - "5000:5000"
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: "INFO"
      REDASH_REDIS_URL: "redis://redis:6379/0"
      REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
      REDASH_COOKIE_SECRET: veryverysecret
      REDASH_WEB_WORKERS: 2
    volumes:
      - "sqlite_sakila:/sqlite:rw"
    restart: always
  worker:
    image: redash/redash:3.0.0.b3147
    command: scheduler
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: "INFO"
      REDASH_REDIS_URL: "redis://redis:6379/0"
      REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
      QUEUES: "queries,scheduled_queries,celery"
      WORKERS_COUNT: 4
    volumes:
      - "sqlite_sakila:/sqlite:ro"
    restart: always
  redis:
    image: redis:3.0-alpine
    restart: always
  postgres:
    image: postgres:9.5.6-alpine
    restart: always
  nginx:
    image: redash/nginx:latest
    ports:
      - "80:80"
    depends_on:
      - server
    links:
      - server:redash
    restart: always
  sakila:
    image: rnoennig/sakiladb
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
    restart: always
  pagila:
    image: mujz/pagila
    restart: always
volumes:
  sqlite_sakila:

docker-compose.yml が準備できたら、Redashのデータベースを初期化するため、以下のコマンドを実行します。

$ docker-compose run --rm server bash -c "sleep 15 && /app/bin/docker-entrypoint create_db"
$ docker-compose up

公式ドキュメントで説明されているコマンドと少し違っていますが、PostgreSQLの起動を待たずにマイグレーションスクリプトが動いてしまうことがあるので、その対策としてsleepを15秒挟んでいます。

ちなみにこの問題はPRを送っているので、もしかしたら今後は起こらなくなるかもしれません。

github.com

Redashが起動したらhttp://localhostにアクセスし、admin ユーザーを作成します。

admin ユーザーの作成後、データソースを追加するため、docker-composeを実行しているものとは別のターミナルで、以下のコマンドを実行します。

$ docker-compose exec -u root server chmod a+rw /sqlite
$ docker-compose exec server wget -O /sqlite/sqlite-sakila.sq https://github.com/wallymathieu/sakila-sample-database-ports/raw/master/sqlite-sakila-db/sqlite-sakila.sq
$ docker-compose exec server ./manage.py ds new --type mysql --options '{"db": "sakila", "host": "sakila", "user": "root"}' sakila
$ docker-compose exec server ./manage.py ds new --type pg --options '{"dbname": "pagila", "host": "pagila", "password": "admin", "user": "root"}' pagila
$ docker-compose exec server ./manage.py ds new --type sqlite --options '{"dbpath": "/sqlite/sqlite-sakila.sq"}' sqlite-sakila
$ docker-compose exec server ./manage.py ds new --type results results

これらのコマンドはRedashのCLIを使用してデータソースを追加しています。

クエリの作成

Query Resultsデータソースの動作確認に使用するクエリをRedashの画面上で作成します。

クエリIDを変えてしまうと後述のサンプルクエリが動かなくなってしまうため注意してください。

クエリID1としてsakilaデータソース(MySQL)を使用して作成

SELECT
    country_id,
    country
FROM
    country
WHERE
    country_id BETWEEN 100 AND 110
;

クエリID2としてpagilaデータソース(PostgreSQL)を使用して作成

SELECT
    country_id,
    COUNT(*) num_of_cities
FROM
    city
GROUP BY
    country_id
;

クエリID3としてsqlite-sakilaデータソース(SQLite)を使用して作成

SELECT
    city.country_id,
    COUNT(*) num_of_addresses
FROM
    address
JOIN
    city ON address.city_id = city.city_id
GROUP BY
    city.country_id
;

ここまでで環境構築は完了です。

Query Resultsデータソースを使用したクエリを実行する

公式ドキュメントにも説明があるとおり、QUERY_[クエリID](小文字でも良い)という記法で対象のクエリを指定します。

redash.io

以下の例は、データソースが異なるクエリ1、2、3それぞれの結果をJOINして、各テーブルのカラムをSELECTする例です。

SELECT
    mysql.country,
    pgsql.num_of_cities,
    sqlite.num_of_addresses
FROM query_1 mysql
JOIN query_2 pgsql ON mysql.country_id = pgsql.country_id
JOIN query_3 sqlite ON pgsql.country_id = sqlite.country_id
;

実行すると、以下のような結果が表示されます。

f:id:ariarijp:20171130211401p:plain

異なるデータソースを使用したクエリの「結果」に対してクエリを実行できていることがわかりました。

内部的にはSQLiteをインメモリで使用しているので、SQLiteで使えるSQLをそのまま使えます。

テーブル名の書き方について

現時点ではテーブル名の前に改行やタブなど、半角スペース以外の空白文字が入ってしまうと、該当のクエリを発見できない問題がありますが、PRをマージしていただいたので、4.0.0で修正されると思います。

github.com

注意点

ドキュメントに記載があるように、現時点では以下の注意点があります。

  • Query Resultsデータソースを使用したクエリは、クエリ内で参照する他のクエリを常に再実行する
  • オンメモリのSQLiteに結果を保持するため、メモリ不足が起きると失敗する
  • クエリ内で参照する他のクエリが使用しているデータソースすべてに対して権限を持っていないと実行できない

使い所

すべてのケースで有用かというとそうでもないかもしれませんが、異なるデータソースの結果を結合する以外にも、複雑な集計をする際に、中間テーブルの代替としてケースで活用することができると考えました。

まとめ

v3.0.0の機能の中でも大きな機能のひとつだと言えるQuery Resultsデータソースについて、簡単にまとめます。

  • v3.0.0でQuery Resultsデータソースが使えるようになる
  • クエリの「結果」に対してクエリを実行するため、参照するクエリのデータソースがなんであろうと使用できる
  • 複数のデータソースをまたがるクエリを書く場合や、中間テーブルの代替としても使用することができる
  • まだ安定リリース前かつ、使用上の注意点もあるため、本番運用を検討する場合は検証が必要

2日目の記事

明日は kyoshidajp さんが「Redashパラメータ一覧」について記事を書いてくださるようです。お楽しみに。

サービスアカウントのJSON鍵ファイルでPythonからGoogleのAPIを使用する

バッチを書くときにサービスアカウントを使う機会が多いのでメモ。

公式ドキュメントではサービスアカウントを使う例が紹介されていないので、Sheets APIのサンプルコードを少し改変してサービスアカウントで認証するようにしてみる。

Python Quickstart  |  Sheets API  |  Google Developers

from __future__ import print_function

import httplib2
from apiclient import discovery
from oauth2client import tools
from oauth2client.service_account import ServiceAccountCredentials

try:
    import argparse

    flags = argparse.ArgumentParser(parents=[tools.argparser]).parse_args()
except ImportError:
    flags = None

# If modifying these scopes, delete your previously saved credentials
# at ~/.credentials/sheets.googleapis.com-python-quickstart.json
SCOPES = 'https://www.googleapis.com/auth/spreadsheets.readonly'
CREDENTIALS_FILE = 'credentials.json' # サービスアカウントの鍵ファイル名
APPLICATION_NAME = 'Google Sheets API Python Quickstart'


def get_credentials():
    """サービスアカウントで認証する"""
    return ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, SCOPES)


def main():
    """Shows basic usage of the Sheets API.

    Creates a Sheets API service object and prints the names and majors of
    students in a sample spreadsheet:
    https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
    """
    credentials = get_credentials()
    http = credentials.authorize(httplib2.Http())
    discoveryUrl = ('https://sheets.googleapis.com/$discovery/rest?'
                    'version=v4')
    service = discovery.build('sheets', 'v4', http=http,
                              discoveryServiceUrl=discoveryUrl)

    spreadsheetId = '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms'
    rangeName = 'Class Data!A2:E'
    result = service.spreadsheets().values().get(
        spreadsheetId=spreadsheetId, range=rangeName).execute()
    values = result.get('values', [])

    if not values:
        print('No data found.')
    else:
        print('Name, Major:')
        for row in values:
            # Print columns A and E, which correspond to indices 0 and 4.
            print('%s, %s' % (row[0], row[4]))


if __name__ == '__main__':
    main()

実行してみると、公式ドキュメントのサンプルコードだとURLが表示されてブラウザで認証する形になるが、サービスアカウントを使った場合は認証をパスしたものとして処理が続行される。

$ python main.py
Name, Major:
Alexandra, English
Andrew, Math
Anna, English
Becky, Art
Benjamin, English
...略...

最近BigQueryを使ったときもこのやりかたを使った。

Google BigQuery

Google BigQuery