そーす

福岡在住のプログラマ。SwiftとかKotlinとかJavascriptとかSketchとか触ってます。

RedashのデータソースにGoogleAnalyticsを追加する

f:id:saburesan:20170112205438p:plain

*2017年1月15日現在ではRedashのGoogleAnalytics対応はαバージョンということになっているので、今後方法が変わるかもしれません。

Google Developer Consoleでプロジェクトを作成

console.developers.google.com

f:id:saburesan:20170117151718p:plain

サービスアカウントキーを登録する

左側のナビゲーションの「認証情報」→「認証情報を作成」→「サービスアカウントキー」を選択します。

f:id:saburesan:20170116072001p:plain

「サービスアカウント」は「新しいサービスアカウント」を選びます。

サービスアカウント名を入力してキーはJSONにして「作成」を押してサービスアカウントの作成は完了です。

f:id:saburesan:20170117143435p:plain

この時サービスアカウントIDはメモしておきましょう。 また、JSONファイルがダウンロードされますが、これもRedash連携の時に必要です。

Google Analyticsにサービスアカウントを登録する

連携させたいGoogleAnalyticsの「管理」タブ→「ユーザ管理」からサービスアカウントのメールアドレスのユーザを登録します。 権限は「表示と分析」で大丈夫です。

f:id:saburesan:20170117144242p:plain

これでサービスアカウントとGoogleAnalyticsの連携は完了です。

Redashと連携

ここまでくればあとはRedashのデータソース追加画面で

「Type」を「Google Analytics」にして、GoogleDeveloperConsoleでダウンロードしたJSONファイルをアップロードすれば完了です。

f:id:saburesan:20170117144857p:plain

クエリを作成する

GoogleAnalyticsのクエリは0から作るのは非常に大変なので、QueryExplorerというサービスを使いましょう。

Query Explorer — Google Analytics Demos & Tools

たとえば、1ヶ月分の日次新規ユーザを見たい場合は以下のような入力になります。

f:id:saburesan:20170117151541p:plain

これを入力して「Run Query」を押すと画面したの方に結果が得られますが、 その中の「Direct link to this Report」のGETパラメータ(画像赤枠の部分)をコピーします。

f:id:saburesan:20170117150843p:plain

あとはコピーしたパラメータをRedashの「New Query」でクエリとして保存すれば完了です。

まとめ

RedashとGoogleAnalyticsの連携について解説しました。 ブログ執筆現在ではα版ですので変更が予想されますので上手くいかなくなるかもしれませんがご了承ください。

Redash概要

f:id:saburesan:20170112205438p:plain

redash.io

概要

Redashはデータの分析や可視化を効率よく行えるBIツールです。

豊富なデータソースとクエリの登録

Redashは連携データソースとして登録できるソフトウェアやサービスが豊富です。 2017年1月12日現在でこれだけのデータソースに対応しています。 f:id:saburesan:20170112205209p:plain

接続しているデータベースに対してsqlのクエリやAPIのパラメータなどを記録できます。 sqlの場合はカラム名は補完も効くのでクエリを書くのが非常に楽です。 また、snippetを登録できるのでよく使うクエリなどを登録すると補完で使うことが出来ます。

https://redash.io/assets/images/temp/browser1.png Redash公式サイトより転載

可視化が容易

クエリの結果を可視化するときの可視化パターンが豊富です。折れ線グラフやコホート、パイチャートなど大体必要そうなものは揃っています。

https://redash.io/assets/images/temp/browser2.png

Redash公式サイトより転載

外部連携

外部サービスとの連携も容易です。 例えばSlack botにも公式で対応しています。

qiita.com

またグラフなどを外部サービス上で表示する機能も提供されていて、現状はiframeと画像形式で提供されています。 データの可視化に関しては豊富に取り揃えていますが、それらをまとめて閲覧するようの「Dashboard」機能では横1 or 2カラムしか対応しておらず1画面に表示する量が少ないです。 なので閲覧画面のみ自社の管理画面などで自由にカスタマイズすることができます。

新規登録

今回はOSSの方ではなく有料プランの方で勧めます(2017年1月12日現在ではトライアル期間があります) 下記新規登録ページから新規登録します。 https://app.redash.io/signup

フォームを入力するとすぐにDashboardへ遷移します。

Dashboard

f:id:saburesan:20170113070055p:plain

画像はログイン直後の画面です。 今回よく使う機能のみ紹介します。

Dashboards

Dashboardとは複数の可視化したグラフなどを纏めて閲覧できる機能です。 ヘッダーの「Dashboard」ボタンをクリックすると現在作られているDashboardの一覧と新規Dashboardへのリンクが表示されます。

Queries

Queriesは接続したデータソースに対するクエリやパラメータを保存する機能です。 ヘッダーのQueries->New Queryから以下の画面へ遷移します。 f:id:saburesan:20170113071327p:plain

Query編集画面ではクエリの編集とそのクエリの結果をもとにどのように可視化するかの編集ができます(後日解説)

Data source

f:id:saburesan:20170113071923p:plain

データソースを登録出来ます。 私はMySQLとGoogleAnalyticsを使ったことがあります。 MySQLの連携は難しくないですが、GoogleAnalyticsの連携が個人的に色々と結構面倒くさかったので後日まとめたいと思います。 MySQLを登録する場合、RedashはIPアドレス52.71.84.157からMySQLへアクセスするので事前にこのIPからのアクセスを許可する必要があります。

f:id:saburesan:20170113073414p:plain

あとはフォームに必要な項目を入力して接続テストが成功すれば登録完了です。 接続したデータソースからQueryを登録するには、「New Query」画面の「Data Source」の一覧から選択することで可能です。

まとめ

かなりざっとしたRedashのDashboardの紹介になってしまいましたが、今後はもっと細かい機能の使い方を紹介したいと思います。

ミラースタンドを自作した

f:id:saburesan:20170110091603j:plain

うちには長らくミラーがなくて、欲しいやつとかは結構高かったので作ることにした。 写真は完成品。

  1. ニトリのミラー
  2. ナフコの木材

ウォールミラー(ソレイユ) | ニトリ公式通販 家具・インテリア・生活雑貨通販のニトリネット

金額

2500円位かな

パームレスト自作2

前回初めて自作して思いの外良かったので職場+家用で2個欲しくなり作りました

材料は前回と同じなのですが、今回は2つあるうちの1つを6mmの厚さの板に変更しました。

f:id:saburesan:20170107100113j:plain

厚さがHHKBの土台の高さピッタリになりました!!!!!!

あー、きもちぃぃーーーーー

ちなみに今回はナイフ形のヤスリを購入して手首が当たる部分の角を結構滑らかにするように加工しました。

接着するときもおもりを乗せて隙間が出来ないようにしたりと、

f:id:saburesan:20170107100836j:plain

前回の反省を活かして制作した甲斐があってか、前回よりも使いやすいものに仕上がりました。

厚さも軽さもこっちのほうが良いのでこれをもう一つ作ろうかな。

(ちなみに前回作ったパームレストはディスプレイスタンドとして活躍してます)

データが無い日をUnionで補完する

BIツールなどを使ってグラフとかを日付順に出したい時に

「今日の〇〇のデータは無いけど△△では今日のデータあるので〇〇のグラフも今日の表示が欲しい」

みたいなことがある場合の対処法です。

Unionでデータを追加する

DBにデータを入れるわけには行かないのでクエリの結果にのみ入れるようにします。

例えばpaymentsというテーブルから過去1ヶ月分の日毎の支払件数を出したい場合は、

SELECT date(paid_at) AS date,
       count(amount) AS COUNT
FROM payments
WHERE paid_at IS NOT NULL
  AND date(paid_at) >= date_sub(CURRENT_DATE, interval 1 MONTH)
GROUP BY date
ORDER BY date DESC

こんな感じでとりあえずはとれます。

ただ、今日取引が無かった場合はグラフには表示されません(X軸は昨日までの表記になる)

そこでUNIONを使ってダミーデータを追加します。

SELECT date(t.paid_at) AS date,
       count(t.amount) AS COUNT
FROM (
        (SELECT paid_at,
                amount
         FROM payments
         WHERE paid_at IS NOT NULL)
      UNION
        (SELECT CURRENT_DATE AS paid_at,
                                NULL AS amount)) t
WHERE t.paid_at IS NOT NULL
  AND date(t.paid_at) >= date_sub(CURRENT_DATE, interval 1 MONTH)
GROUP BY date
ORDER BY date DESC

ポイントはUNIONはカラム数カラム名を同じにしなければならないことです。 ダミーデータの方をpaymentsテーブルのカラムに合わせてもよいのですが、 カラムが多いと面倒なので、元のpaymentsテーブルも必要なカラムのみをSELECTするようにしたほうが楽なんじゃないかなと思います。

UNIONじゃなくて他にも方法あるのかなー

不要なCOUNT関数を追加すると何故か高速化するSELECT

最近データ分析をやることが多くSQLばっかり書いてます。

といってもSQLとかDBに関する知識はMAX100とすると私は「2」くらいです。

なので、色々と躓きます。

昨日もちょっとわからないことがありました。

例えば、日毎の累計ユーザが欲しい場合のクエリは

SELECT date(created_at) AS created_on,
  (SELECT count(*)
   FROM users
   WHERE date(created_at) <= created_on) AS COUNT
FROM users t
WHERE t.created_at >= date_sub(CURRENT_DATE, interval 7 DAY)
GROUP BY created_on
ORDER BY created_on DESC

こんな感じになるかと思います。

これは最近の1週間分なんですが、コレだとめちゃくちゃ遅いです。

サブクエリでも毎回集計してるから遅いのは仕方ないとは思うのですが、

このSELECTに

COUNT(*) AS today_count,

というのを追加して

SELECT date(created_at) AS created_on,
       COUNT(*) AS today_count,

  (SELECT count(*)
   FROM tokopay_users
   WHERE date(created_at) <= created_on) AS COUNT
FROM tokopay_users t
WHERE t.created_at >= date_sub(CURRENT_DATE, interval 7 DAY)
GROUP BY created_on
ORDER BY created_on DESC

とすると、何故か爆速になりました。

私には全く理由がわかりません。

誰か教えて欲しい。

HHKBのパームレストを100均アイテムで自作した話

HHKBめっちゃ使いやすいんだけど、私は指が短いので一番上の列のキーが結構遠い。

MBPのキーボードの時は特に感じなかったので、おそらくHHKB自体の高さがある分距離が長くなったんだろうと思い

パームレストが欲しくなりました。

売ってるやつは4000円とかなんで、ちょと高いなーと思ってたら

結構自作してるひといるみたいだったので自分も100均を漁りに行って作りました。

はいこれ

f:id:saburesan:20170106081618j:plain

材料

  • 100円木材(40 * 12 * 9) を2つ
  • 100円木工用ボンド
  • 100円紙やすり
  • オイルステイン塗料(家にたまたまあった)
  • つや消しスプレーニス(家にたまたまあった)

ってことで432円ですね。

作り方

  1. ボンドで木材をくっつける
  2. 角を取るようにヤスリで削る
  3. オイルステインを塗る
  4. スプレーニスを吹きかける
  5. 完成!

使用感

結構良い。

土台よりも若干高さが出てしまったなーと思っていたけれど

手をおいたときに自然とパームレストとスペースの高さが同じになったため

親指の位置がちょうどよい。

f:id:saburesan:20170106082938j:plain

横幅が長いので時間あるときに切ろうかな。

Google Dagger2の公式サンプルをKotlinで書き直してみた

これをAndroid Kotlinで書き直しました。

元のサンプル github.com

Android Kotlin Ver

GitHub - ryohlan/Dagger2KotlinSample: Dagger2 sample written by Kotlin

経緯

Daggerに関しては色々記事は読んだもののイマイチわからなくて、かと言って使わないといけない状況も無かったので放置していた。

最近Android界隈も目新しいことがあまりなくなって(来たように感じて)きて、なんか新しいこを学ばないとなーと思っていた。

テスト

Androidではちゃんとテスト書いたこと無いし、テストを意識したような設計もやったことないです。ちょっとして機能のテストなら書いたことあるくらい。

Android Clean Architecture は避けてた勢です。

そろそろちゃんとそのあたり学ばなと行けないと思い、Android Clean ArchitectureとDaggerを採用して個人アプリを作りながら理解を深めようと思っている。

UIScrollViewは基本的にUIStackViewを内包していいのでは

iOS8を切れる状況なら、ですが。

class StackScrollView: UIScrollView {
    private(set) lazy var stack: UIStackView = {
        let stack = UIStackView()
        stack.axis = .vertical
        stack.alignment = .center
        return stack
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        addSubview(stack)
        stack.snp.makeConstraints { (make) in
            make.top.equalTo(self)
            make.left.equalTo(self)
            make.right.equalTo(self)
            make.centerX.equalTo(self)
        }
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        contentSize = stack.frame.size
    }

 func push(view: UIView) {
        stack.addArrangedSubview(view)
    }
}

レイアウトにはSnapKit使ってます。 github.com

色なめんどくさいことから解放される感。

カラムの情報をモデルに出力するGem「annotate」

f:id:saburesan:20161031001347p:plain

最近個人アプリでサーバーサイドも作りたくなったので目下勉強中です。

Railsチュートリアルは一通りやってたのですが、アプリのAPIとなると結構わからない部分が多くて中々進みません。

テーブルのカラムの情報をschema.rb見ながらモデルクラスにバリデーション書いたりするのが結構めんどくさいなーと思っていたら、

カラム情報をモデルクラスに出力してくれるgemがあることを知りました。初学者にはありがたい。

インストール

gem 'annotate'
bundle install

カラム情報を書き込む

カラムに変更を加えたら以下を実行

bundle exec annotate

すると

# == Schema Information
#
# Table name: users
#
#  id               :integer          not null, primary key
#  email            :string(180)
#  crypted_password :string(255)
#  salt             :string(255)
#  created_at       :datetime
#  updated_at       :datetime
#  token            :string(180)
#  hoge             :string(255)
#

class User < ActiveRecord::Base
  authenticates_with_sorcery!

  validates_uniqueness_of :email, allow_blank: true
end

Retrofitで503の時にメンテナンスモードを表示する

f:id:saburesan:20160906091652j:plain

Kotlinは特に関係ないけど。

Retrofitって503ってデフォルトでリクエスト成功の処理に回されるんですかね?

私の環境だと503を成功として扱っていて困りました。

また、デフォルトで成功処理の場合はステータスコードが取得出来ないのでRetrofitでAPIのinterfaceの定義を変更する必要がありました。

ただ、もともとSchedulerのの設定などを共通で行うExtensionを作っていたので変更はすぐに出来ました。

以下変更点

・成功時もステータスコードを取得できるように

//変更前
@Headers("Accept: application/json")
@GET("setup")
fun getSetUp(): Single<SetupResponse>

//変更後 retrofit2のResponseクラスを使う
@Headers("Accept: application/json")
@GET("setup")
fun getSetUp(): Single<Response<SetupResponse>>

・Schedulerなどの共通処理

//変更前
fun <T> Single<T>.androidDefaultSubscribe(subscriber: ApiSubscriber<T>): Subscription {
    return observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(subscriber)

//変更後
fun <T> Single<Response<T>>.androidDefaultSubscribe(subscriber: ApiSubscriber<T>): Subscription {
    return observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.io()).subscribe(object : SingleSubscriber<Response<T>>() {
        override fun onSuccess(value: Response<T>?) {
            value?.let {
                if (it.isSuccessful) {
                    subscriber.onSuccess(it.body())
                } else {
                    onError(error = HttpException(it))
                }
            } ?: onError(error = null)
        }

        override fun onError(error: Throwable?) {
            subscriber.onError(error)
        }
    })
}

・エラーメッセージを整形する処理 SingleSubscriberを継承した抽象クラスです。 レスポンスがエラーの場合はエラーメッセージを配列で受け取るのでそれらを表示する形式に整形して返します。 statusCodeが503の場合にメンテナンス用Activityを別タスクで起動して処理を終了させます。

abstract class ApiSubscriber<T> : SingleSubscriber<T>() {
    override fun onError(error: Throwable?) {
        error?.printStackTrace()
        val httpException = error as? HttpException
        val statusCode = httpException?.code() ?: 0
        val errorMessage = when (statusCode) {
            0 -> App.context.getString(R.string.network_error)
            503 -> {
                MaintenanceActivity.start()
                return
            }
            else -> httpException?.response()?.errorBody()?.string()?.let { GsonWrapper.gson.fromJson(it, ErrorMessagesResponse::class.java)?.errors?.reduce { s1, s2 -> "$s1\n$s2" } }
                    ?: App.context.getString(R.string.network_error)
        }
        onError(ErrorResponse(statusCode = statusCode, message = errorMessage))
    }

    abstract fun onError(errorResponse: ErrorResponse)
}

・メンテナンス用Activity

class MaintenanceActivity : AppCompatActivity() {
    private val view by lazy {
        val webView = WebView(this)
        webView.setWebViewClient(WebViewClient())
        webView
    }

    companion object {
        fun start() {
            val intent = Intent(App.context, MaintenanceActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
            App.context.startActivity(intent)
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(view)
        view.loadUrl(MAINTENANCE_URL)
    }
}

文章中の任意のテキストをクリック可能にする

f:id:saburesan:20160427075732p:plain

UITextFieldを使う

というかUITextFieldじゃないとクリックイベントが発火しないようです。 UILabelとかUITextViewはだめみたいです。

クリック可能な部分のテキストのスタイル定義

UITextFieldのlinkTextAttributesプロパティにスタイルをセットします。

たとえば、テキストを白にして下線を付けるとすると

textField.linkTextAttributes = [NSForegroundColorAttributeName: UIColor.white NSUnderlineStyleAttributeName: 1]

こうなります。 リンクテキストのスタイルはattributedTextプロパティでは上書きできないのでlinkTextAttributesを必ずセットする必要があります。

NSMutableAttributedStringを作成する

リンクを含む全体のテキストで初期化します。

let attr = NSMutableAttributedString(string: "テキスト、リンク1、リンク2")

リンクさせたいテキストの位置を取得する

let startIndex = attr.string.distance(from: attr.string.startIndex, to: attr.string.range(of: "リンク1")!.lowerBound)

これでstatIndexには文章中のリンク1の先頭位置が入ります。 決め打ちでやるよりも、文章の変更や多言語対応などを考えると計算したほうがよいでしょう。

あとはスタイルをattrに追加するだけです。

attr.addAttribute(NSLinkAttributeName, value: "開くURL", range: NSRange(location: startIndex, length: "リンク1".characters.count))
textField.attributedText = attr

Statusbarのスタイルを変更する

f:id:saburesan:20160427075732p:plain

LaunchScreenとここのViewControllerごとに変更しなければならないようです。

LaunchScreenの変更

Info.plistにStatus bar styleを追加します。 f:id:saburesan:20160921132857p:plain

ViewControllerごとの変更

UIViewControllerのpreferredStatusBarStyleプロパティをオーバーライドしましょう

 override var preferredStatusBarStyle: UIStatusBarStyle { return .lightContent }

結果

f:id:saburesan:20160921133144p:plain

基本的にCustomViewを使うメリット(Adapter編)

class ItemAdapter(context: Context) : ArrayAdapter<Item>(context, 0, mutableListOf<Item>()) {
    var delegate: EventDelegate? = null

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val view = (convertView as? ItemView) ?: ItemView(context)
        view.delegate = delegate
        view.set(getItem(position))
        if (position == count - 1) {
            delegate?.onCalledLastItem()
        }
        return view
    }

    interface EventDelegate : ItemView.EventDelegate {
        fun onCalledLastItem()
    }
}

getViewの中すっきりするよね。