ABEJA Tech Blog

中の人の興味のある情報を発信していきます

Notion DB をイイ感じに活用する Tips 3選 〜巷で人気の音ゲーの楽曲リストを例に〜

はじめに

皆さんこんにちは。
毎年の Advent Calendarで Notion やら Google AppSheet やら業務効率系の記事を書いている齋藤です。

本記事は ABEJA Advent Calendar 2022 の3日目の記事になります。
今回は、私が今年の元日(2022/01/01)公開した「プロセカNote*1を例に、「業務や個人で活用するときにも活きるノウハウであるかどうか」を考慮して工夫した点を3点ピックアップして共有しようと思います。

前提

そもそも、今回私が本気で整備している Notion のページ(プロセカNote)に関して、超簡単にご紹介します。

プロセカNote -楽曲一覧- ギャラリー表示画像
プロセカNote -楽曲一覧-

www.notion.so

プロセカNote とは、私が「趣味: 9, 勉強: 1」の割合で Notion に関して色々触ってみようと思い Notion で作成した web ページのことです。
作成の背景や、その意気込みなどは1年前に私の個人の Blog*2 の方に記載していますので割愛します。
本ブログも基本的にこのプロセカNote で使っているノウハウに関して記載していますので(使っているノウハウの該当箇所には適宜リンクを張っておきます)、参考に一度見てみていただけるとわかりやすいかと思います。*3

プロセカとは

ここでプロセカに関して語ると、とんでもなく長くなるので詳細は省きます。*4
プロセカの正式名称は『プロジェクトセカイ カラフルステージ! feat. 初音ミク』で、初音ミクを始めとするバーチャル・シンガーの楽曲(よく言うボカロ楽曲)を中心にした音ゲーです。
リリース3ヶ月で「Google Play ベスト オブ 2020*5」でのユーザ投票部門の最優秀賞を受賞し、リリースから約2年たった今では、月間アクティブユーザーが、あのパズドラを抜いて4位になるほど、若年層に人気のスマホの音ゲーです。*6

IMG
プレイ画面イメージ*7

概要

冒頭でも書いたとおり、プロセカNoteを作成するにあたって、Notion に関わる様々なツールや技術をのべつ幕なしに利用しています。
今回、その中でも特に工夫した下記の3点に関してご紹介します。

  1. 無料で手軽にグラフが作れる! GASとGSSの連携
  2. 複雑なプロパティ過多問題には命名規則を使おう!
  3. 雑多なプロパティの入力を効率的にする_self relationのススメ

▼ 目次 ※ クリックで目次が展開されます

1. 無料で手軽にグラフが作れる! GASとGSSの連携

課題

Notion DBはページに対してプロパティを付与することによって、一つのページをレコードのように扱うことができます。
なかでも、TableViewを用いた場合は、もう完全に見た目が表計算ソフトのそれ*8になります。 (ちょうど↓の画像みたいな感じ)

Notion DBの見た目

こんな感じの表を見ると、どうしてもグラフ化したくなるのは全世界共通かと思いますが、残念ながらNotionのデフォルトの機能ではNotionDBに入っているデータを直接グラフ化する機能はありません。

「Notion DBの値でグラフを作って表示したい」という要望に対して、もちろん解決策がないわけではありません。 最も簡単な解決策としてはサードパーティ製のサービスである、NotionChartsを利用することです。
このツールに関しては他の様々な方が紹介*9していますし、簡単かつシンプルにグラフを作ることができるので、かなりオススメです。

一方で、このNotionChartsを利用する場合下記のようなデメリットがあります。

  1. 無料で作れるグラフは5つまで
  2. グラフの種類が限られている

これらのグラフを埋め込めるツールを使うにあたって、上限があったり、お金がかかったりしてしまうのはほぼ避けられないのですが、今回紹介する方法は、無料かつ無制限にグラフを追加することができます。

解決策

先の課題の節ですごく期待値を上げましたが、至って簡単な手順で埋め込むことができます。

  1. Google SpreadSheet(以下、GSS)のグラフ生成機能を用いてグラフを生成
  2. グラフを埋め込み形式でリンクを取得
  3. 埋め込みリンクをNotionで埋め込み

ここまではいろんな記事で紹介されていますし、改めてご紹介する必要もないのですが、この手法で次に考えなければいけないのはどうやって、GSSにNotionのデータを入れるのかです。

Notion DBの値をわざわざ手動で毎回GSSでも更新するとなると、かなり面倒ですし、ヒューマンエラーが必ず発生するでしょう。 今回はGoogle AppScript(以下、GAS)とNotion APIを利用して定期的にNotion DBの値を取得して、GSSに転記する処理をご紹介します。
それぞれ、処理の注目ポイントを絞ってご紹介します。(最後に全部まとめた処理紹介します)

シークレット系の情報の扱い

よくGASでAPI叩く処理を見ることがあるのですが、結構なパターンでクレデンシャル情報が直書きされていたりします。
確かに共有さえしなければ見られることはありませんが、あまり褒められた実装ではないので、きちんと環境変数に入れるところからやります。

手順

  1. GSSからGASのスクリプト編集画面を開く
  2. ファイルを追加で constant.gs を作成(ファイル名は任意です)
  3. 下記のコードを投入
  4. PropertiesService.getScriptProperties()setProperty関数の引数を変更してkeyと値を設定
    • 例) PropertiesService.getScriptProperties().setProperty('sampleID', 'ThisIsSampleID')
  5. setEnv関数をデバッグ or 通常の実行で値を投入
  6. (値を確認) getEnv関数をデバッグ or 実行

constant.gs

function setEnv() {
  PropertiesService.getScriptProperties().setProperty('hoge', '')
}
function getEnv() {
  Logger.log('NotionToken: ' + PropertiesService.getScriptProperties().getProperty('NotionToken'))
  Logger.log('dbId: ' + PropertiesService.getScriptProperties().getProperty('dbId'))
}

解説
やっていることは単純で、PropertiesService.getScriptProperties()setProperty関数を使ってkeyと値を設定して、環境変数にクレデンシャル情報を保存。 終わったら、クレデンシャル情報を消しておく。
クレデンシャル情報が見たい場合はPropertiesService.getScriptProperties()getProperty関数を使って値を取得するだけです。

これでクレデンシャル情報や直書きしておきたくない定数を環境変数に入れることができました。

--- 追記: 2022/12/05 ここから ---
GASの環境変数ですが、旧UIから現在のUIに変わった際に消失していたと思っていたのですが、どうやらどこかのバージョンアップのタイミングで、GUIでも設定できるようになっていたようです。
こちら、GASのサイドバーの「プロジェクトの設定」から、「スクリプト プロパティ」の項目を編集することで、新規環境変数の保存や、既存のKeyと値を確認ができるようです。
ご参考まで。
--- 追記: 2022/12/05 ここまで ---

Notion APIでの値の取得

この章の一番のキーポイント間違いなくここでしょう。

今回使うAPIはこのNotion API(https://developers.notion.com/reference/intro)です。

最初にNotionAPIを使えるようにインテグレーションする必要がありますが、こちらはNotion側がめちゃめちゃわかりやすいGif画像を用意してくれているのでそちらをご共有します。
↓のページにインテグレーションの手順が全て書かれています。

developers.notion.com

IMG

※ こちらで取得した Internal Integration Token は認証のときに利用します。

Notionのデータは大量にある場合、取得しようとしても一度では取得し切ることはできません。 データが最後まで取得できなかった場合、取得時のレスポンスbodyに、has_more というbool値と、その次のデータの始点を指定するためのnext_cursorが返却されるため、ここの条件分岐は非常に簡単です。

値の取得のコードサンプルはこちらです

/**
 *  NotionAPIをCallしてデータを一つにまとめる再帰関数
 *  @parms: dbId: string: データベースのID
 *  @parms: corsor: string: データ取得時に指定するカーソル
 *  @return: 結果配列
 */

function _api(dbId, cursor = undefined) {
  const url     = `https://api.notion.com/v1/databases/${dbId}/query`,  // Note: APIのエンドポイント
        token   = PropertiesService.getScriptProperties().getProperty('NotionToken');  // 認証用のToken

  let headers   = {  // Note: 認証ヘッダーはこちら。
    'content-type' : 'application/json; charset=UTF-8',
    'Authorization': `Bearer ${token}`,
    'Notion-Version': '2022-06-28',
  },
    payload   = {  // Note: 開始cursorと一度に取得するページ(レコード数)の指定
      'start_cursor': cursor,
      'page_size': 100
    },
    options   = {
      'method': 'post',  // Note: データの取得だけれど、POSTなのにご注意!!
      'headers': headers,
      'contentType': 'application/json',
      'payload' : JSON.stringify(payload)
    },
    res       = JSON.parse(UrlFetchApp.fetch(url, options));  // Note: Call API

  // 結果レコードが残っている場合、再帰呼び出しを実行。取得した結果をすべて結合して返却
  if (res.has_more) {
    return [
      ...res.results,
      ..._api(dbId, res.next_cursor)
    ]
  }
  return res.results  // 最後のレコードを読み込んだときはそのまま返却(再帰呼び出しの終点)
}
GASからGSSへの書き込み

まず取得したデータですが、どのようなデータを取得するのかによって結果データのフォーマットが異なります。 Object配列の結果Objectを簡単にまとめたものが下の表になります。

データType データ格納先 備考
Title properties[<プロパティ名>].title[0].plain_text ページ(レコード)のタイトル
必須入力で他のデータと似たようなObjectに格納
Icon icon.file.url ページに指定されているIconのURL
text properties[<プロパティ名>].rich_text[0].plain_text 文字列のプロパティ
タイトルとはtitlerich_textの違いのみ
number properties[<プロパティ名>].number 数値のプロパティ
素直にnumberを指定
date properties[<プロパティ名>].date.start 日付のプロパティ
開始日時と終了日時を指定しない場合はstartにデータが格納され、指定されている場合はそれぞれstart, endにデータが格納
formula(number) properties[<プロパティ名>].formula.number formulaによる計算結果のプロパティ
今回は結果がnumberなのでnumberだが、それ以外の場合はそのフォーマットに従って変化

あとは、GAS上のデータをGSSに書き込む処理ですが、これに関する記事は星の数ほどあるので、コードベースでご紹介します。

/**
 *  楽曲名: ページのタイトル
 *  ID: 文字列
 *  IconUrl: ページで指定しているアイコンのファイルのURL
 *  BPM: 数値
 *  Time: functionで出力された数値
 *  Lyric: 作詞者名の文字列
 *  Composer: 作曲者名の文字列
 *  Arranger: 編曲者名の文字列 
 *  Date: 配信日の文字列(end dateの指定はなし)
 */
function getDbData() {
    let dbId = PropertiesService.getScriptProperties().getProperty('dbId'),
      res  = _api(dbId);
  return res.map((element) => ({  // Note: 値が入っていない場合はundefinedを回避
    Title: element.properties['楽曲名'].title[0].plain_text,
    ID: element.properties['ID'].rich_text[0]?.plain_text || '',
    IconUrl: element.icon?.file?.url || '',
    BPM: element.properties['BPM'].number || '',
    Time: element.properties['time(s)'].formula?.number || '',
    Lyric: element.properties['作詞'].rich_text[0]?.plain_text || '',
    Composer: element.properties['作曲'].rich_text[0]?.plain_text || '',
    Arranger: element.properties['編曲'].rich_text[0]?.plain_text || '',
    Date: element.properties['配信日'].date?.start || ''
  }));
}

// 取得したデータをGSSに書き込む関数
function setMusicDbData(data) {
  let sheet   = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('SyncedMusicDB'),  // Note: GSSのシート名を指定
      result  = data.map((element) => ([  // Note: 各データを投入するため二次元配列へ変換
        element.Title,
        element.ID,
        element.IconUrl,
        element.BPM,
        element.Time,
        element.Lyric,
        element.Composer,
        element.Arranger,
        element.Date
      ]));
  // Note1: GASとGSSの連携的に二次元配列化して setValuesで 入れたほうが処理が高速
  // Note2: 1行目をヘッダーにしているのでgetRangeの始点をA2(2, 1,...)のセルに指定
  sheet.getRange(2, 1, result.length, 9).setValues(result);
  Logger.log(result.length)
}
全部まとめた処理

大まかな処理は以上です。 最後に処理全体のコードを↓のToggleにまとめておきます。

▼ GAS側処理ファイル

constant.gs

function setEnv() {
  PropertiesService.getScriptProperties().setProperty('hoge', '')
}

function getEnv() {
  Logger.log('NotionToken: ' + PropertiesService.getScriptProperties().getProperty('NotionToken'))
  Logger.log('dbId: ' + PropertiesService.getScriptProperties().getProperty('dbId'))
}

main.gs

// Note: 定期実行main関数
function main() {
  let res = getDbData();
  setDbData(res);
}

/**
 *  楽曲名: ページのタイトル
 *  ID: 文字列
 *  IconUrl: ページで指定しているアイコンのファイルのURL
 *  BPM: 数値
 *  Time: functionで出力された数値
 *  Lyric: 作詞者名の文字列
 *  Composer: 作曲者名の文字列
 *  Arranger: 編曲者名の文字列 
 *  Date: 配信日の文字列(end dateの指定はなし)
 */
function getDbData() {
    let dbId = PropertiesService.getScriptProperties().getProperty('dbId'),
      res  = _api(dbId);
  return res.map((element) => ({
    Title: element.properties['楽曲名'].title[0].plain_text,
    ID: element.properties['ID'].rich_text[0]?.plain_text || '',
    IconUrl: element.icon?.file?.url || '',
    BPM: element.properties['BPM'].number || '',
    Time: element.properties['time(s)'].formula?.number || '',
    Lyric: element.properties['作詞'].rich_text[0]?.plain_text || '',
    Composer: element.properties['作曲'].rich_text[0]?.plain_text || '',
    Arranger: element.properties['編曲'].rich_text[0]?.plain_text || '',
    Date: element.properties['配信日'].date?.start || ''
  }));
}

// 取得したデータをGSSに書き込む関数
function setMusicDbData(data) {
  let sheet   = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('SyncedMusicDB'),
      result  = data.map((element) => ([
        element.Title,
        element.ID,
        element.IconUrl,
        element.BPM,
        element.Time,
        element.Lyric,
        element.Composer,
        element.Arranger,
        element.Date
      ]));
  sheet.getRange(2, 1, result.length, 9).setValues(result);
  Logger.log(result.length)
}

/**
 *  NotionAPIをCallしてデータを一つにまとめる再帰関数
 *  @parms: dbId: string: データベースのID
 *  @parms: corsor: string: データ取得時に指定するカーソル
 *  @return: 結果配列
 */
function _api(dbId, cursor = undefined) {
  Logger.log(`cursor: ${cursor}`)
  const url     = `https://api.notion.com/v1/databases/${dbId}/query`,
        token   = PropertiesService.getScriptProperties().getProperty('NotionToken');
  let headers   = {
    'content-type' : 'application/json; charset=UTF-8',
    'Authorization': `Bearer ${token}`,
    'Notion-Version': '2022-02-22',
  },
  payload   = {
    'start_cursor': cursor,
    'page_size': 100
  },
  options   = {
    'method': 'post',
    'headers': headers,
    'contentType': 'application/json',
    'payload' : JSON.stringify(payload)
  },
  res       = JSON.parse(UrlFetchApp.fetch(url, options));
  if (res.has_more) {
    return [
      ...res.results,
      ..._api(dbId, res.next_cursor)
    ]
  }
  return res.results
}

学び

今回、かなりガッツリコードを記事に載せましたが、基本的にこれだけでNotionとGSSのデータ連携は完了です。
main関数を任意のスパンで定期実行するようにトリガーを指定してあげれば、あとは勝手にGSSのほうが更新されていきます。

この機構を作ったことで、Webページの表現の幅が大幅に広がりました。
「Notionでできない計算や集計はGSSの方に任せて、Notionにはグラフだけ表示する」
という形にするとより多くの表現が可能になります。

プロセカNoteでのGSSにランキング形式の横棒グラフ・ヒストグラムなどの表現例
プロセカNoteでのGSSによる様々なグラフ表現例

上の画像で上げたのはほんの一部で、これ以外にも「円グラフ」「散布図」「エリアチャート」「ツリーマップ」とめちゃめちゃバリエーションに富んだチャートを、無料無制限に使えるというのが、この手法の一番大きなメリットです。

正直想定しているより実装は簡単で、それにより得られた効果(GSSのチャートの表現の豊富さ)が大きかったので、これは少しでもAPIを触ることができる人は是非試してみてください。


2. 複雑なプロパティ過多問題には命名規則を使おう!

課題

皆さんはNotionDBを使う上で、画像のようになったことはないでしょうか?

NotionDBにおけるプロパティが多すぎてコンテンツが隠れる
プロパティ多すぎ問題

画像のとおり、NotionDBのプロパティが多くなりすぎて

  1. コンテンツにたどり着くまでのロスがある(UXが悪くなる)問題
  2. どのプロパティが何を意味しているのか分けわからなくなる問題

等の問題が発生します。
「プロパティを簡単に増やすことが可能」「リレーション(Relation)つけたら自動で生成される(これ結構大事)」など様々な原因は様々ありますが、結果、このようなNotionDB*10が生成されてしまいます。

Notion側での解決手法

この問題ですが、一応わかりやすい解決策として、Notion側が用意している手法が、「プロパティの表示・非表示設定」と「DBのLock」機能です。

●プロパティの表示・非表示設定

この機能は多くの方がご存知かと思いますが、端的に機能を紹介すると

  • コンテンツ(ページ)を表示する際に、各種プロパティの表示・非表示に関して設定可能
  • 設定項目は以下の3つ
    • 常に表示
    • 値が入力されているときのみ表示
    • 常に非表示

といったことが容易に設定できます。

●DBのLock

この機能は先に紹介した表示・非表示機能よりマイナーですが、強力です。
機能の内容はそのまま、DBで新しくプロパティを追加したり、編集したり、削除することをできなくする機能です。

「簡単に追加変更できてしまうから無法地帯になってしまうのであれば、それを制限してしまえばいい。」

至極まっとうな対応が可能になる機能ですね。

プロセカNoteでの課題

かく言う私の運営するプロセカNoteも同じ問題に直面しました。
原因としては次のものがあります。

① 複数のDBとRelationがあるDB構成にする必要があった

楽曲のDBと言っても、それに関わる別のテーブルは複数存在し、表示をする上でどうしてもリレーションを貼って値のLookupをせざるを得ない状況になってしまった。
eg) 楽曲のDB、難易度レベルDB、コンボ数DB、キャラクターDB、etc...

② ゲーム内で取得できるポイント(以下、スコア)の計算に多くのfunctionプロパティを要する

音ゲーの解析なので、もちろん「難易度や楽曲ごとに遊んだ結果、どの程度のスコアが得られるのか」といった値の算出にも挑戦しています。
しかし、算出には多くのプロパティを利用して算出する必要があり(計算式は下記の通り)、伴って計算結果を格納するfunctionプロパティも大量に生成されてしまっており、結果、大量のプロパティが存在するDBへと進化してしまっています。

▼ スコア計算式*11はこちら

定数説明
 \displaystyle
x = (1ノーツあたりのポイント)\\{S}_{(SUM)}=(総合力)\\{D}_{(Difficulty)}=(難易度定数)\\{n}_{(notes)}=(ノーツ係数)\\{j}_{(judge)}=(判定係数)\\{C}_{(COMBO)}=(コンボ係数)\\{s}_{(skill)}=(スキル効果)\\{F}_{(FEVER)}=(フィーバー係数)\\{{N}_{s}}_{(SumOfNotes)}=(ノーツ数)\\{{N}_{c}}_{(Calculated Notes)}=(換算ノーツ数)={N}_{s} × n
簡易化定数*12
 \displaystyle
S=(総合力)=150,000\\n=(ノーツ係数)=1\\j=(判定係数)=1\\s=(スキル効果)=1\\F=(フィーバー係数)=1\\{N}_{c}=(換算ノーツ数)={N}_{s}
スコア計算式
 \displaystyle
X=(総合ポイント)\\x=\frac{4SDjCsF}{{N}_{s}}=\frac{600,000DC}{{N}_{s}}\\X=\sum^{{N}_{s}}_{i=1}x_i=\sum^{{N}_{s}}_{i=1}\frac{600,000D{C}_{i}}{{N}_{s}}

このようにプロパティが大量に生成されることで
「あれ? この function で使うはずだったプロパティってどれだったっけ?」 「このプロパティ何に使ってたっけ?」など
Notion DB を管理する上で、この問題は結構出てくるかと思います。

解決策

長々と問題点を挙げましたが、結局この問題の根本は、各種プロパティがどんな役割を持っていて、表示 / 非表示機能で適切に管理することが難しことです。

そこで、発想の転換(ではないですが)をして、プロパティ自体の命名にルール(いわゆる命名規則)をもたせることで、非常に管理が楽になります。
次に例として私がよく使っている命名規則を列挙します。

■ 表示状態によるルール

  • 常に表示(Always show)するプロパティ
    • 英語の場合: UpperCamelCase: のように大文字から始まる1単語
    • 表示したい言語での表示
    • 例) Name、 楽曲名、など
  • 値がないときは非表示(Hide when empty)にするプロパティ
    • 英語の場合: lowerCamelCase: のように小文字から始まる1単語
    • 表示したい言語での表示
    • 例) composerName、 編曲者、など
  • 常に非表示(Always hide)するプロパティ
    • _(lowerCamelCase): の形式
    • 例) _createdAt_updatedAt、など

■ 利用者の行動によるルール

  • プロパティを編集したいし、見えるようにしたいとき
    • UpperCamelCase: のように大文字から始まる1単語
    • 表示したい言語での表示
    • 例) Name楽曲名、など
  • プロパティを見えるようにしたいけど編集させたくないとき
    • (補足): これは主にfomulaプロパティで計算した結果を出したいときなどに利用
    • lowerCamelCase: のように小文字から始まる1単語
    • 例) time(s)、 プレイ時間(01:23の文字列を→83(s)の数値に変換したとき)、など
  • プロパティは見えないようにしたいし、編集もさせたくないとき
    • (補足): これもfomulaを利用した計算を格納しているときや、自動生成された値を入れるときなどに利用
    • _(lowerCamelCase): の形式
    • 例) _createdAt__updatedAt、など

■ DB のリレーションプロパティを利用するときのルール
こちらのルールでは、少し条件が分かりづらい(普通にDBの1対多とかではない)ので、プロセカNoteの具体例で補足すると

  • パターンA
    • 【楽曲DB】に対して【レベルDB】と単純に紐付ける
  • パターンB -【楽曲DB】に対して【メンバーDB】と紐付ける
    • プロセカでは歌唱タイプとして3つがあり、それぞれメンバーDBと紐付ける必要がある(下記画像参照)
      1. ゲームオリジナルのセカイ var
      2. 原曲もしくはボーカロイドが歌唱するVirtual Singer var
      3. 当初のメンバー以外が歌唱するAnother Vocal var

1つのDBが同じDBに対して複数のリレーションをつなぐパターンの画像
1つのDBが同じDBに対して複数のリレーションをつなぐパターン

の2パターンに分けています。
このときの命名規則は、下記のとおりです。

  • パターンA
    • 接続先DB名
  • パターンB
    • DB名は入れない
      • 補足) 接続先のDB名を入れると冗長になるので、ここではあえて入れず、なんでこのDBにつないでいるのかがわかる名称にしています

学び

実際に私がこのようにDBに命名規則を取り入れるだけで
「このプロパティ何だったっけ?」
は壊滅しました。

特にプロパティの把握が便利になって一番恩恵を受けたのが fomula を使うときです。
「ユーザーが入力する値」と、「別のfomulaが計算した結果」がひと目で分かるので、計算が間違っているとき、すぐに問題点を確認することができます。
また、似たような値やプロパティを参照することがありますが、この場合の指定するべきプロパティに悩まなくなりました。

今回は私の自分ルールをご紹介しましたが、このようにプロパティの命名規則を整えてあげるだけで、驚くほど管理が楽になりますので、Notionを管理している人は是非取り入れて試してみてください。


3. 雑多なプロパティの入力を効率的にする _self relation のススメ

課題

前章でも上げたとおり、Notion DB のプロパティは加速度的に増えていってしまうという問題があります。
前章では、その解決方法としてプロパティに対する命名規則をつけて管理する方法を提案しました。

一方、
「入力するとき、Hide when emptyだと毎回プロパティを開かないといけない」
「開いたら大量の項目が出てきて、どれと紐付けなきゃいけないかわからない」
といった問題が発生し、プロパティの入力に対してもあまり良くないUXとならざるを得なくなってしまいます。

プロセカNoteの楽曲DBでも、一つの楽曲情報を入れるためには

  1. 楽曲DB: 24項目
  2. レベルDB: 6項目
  3. コンボ数DB: 6項目
  4. ポイントDB: 3項目

の計39項目のプロパティにデータを入れる事になります*13。 さらに、新しい歌唱メンバーが増えた場合

  1. メンバーDB: 12項目
  2. 歌唱グループDB: 3項目

の計15項目のプロパティをを追加することになります。

解決策

Web UI を開発する上で、このように入力する情報が多い場合、よく取られる方法は入力項目のグループ化です。
これに関しては、「 徹底図解。入力フォームのデザイン・UXを高める15の方法 - ポップインサイト 」コチラのブログの画像がとてもわかりやすいかと思います。

関連情報ごとにグループ化*14

画像の通り、たくさんの入力項目がある場合、グループ化することによってかなり入力のUXが良くなります。
これを利用しない手はありません。

「では、どうやって実現するのか?」という疑問が出るかと思いますが、これはこの章のタイトルが答えとなります。

_self relation とは?

_self relationとは、端的に言うと、DBのリレーションプロパティとして、自分自身を指定するプロパティを準備してあげることです。
プロセカNote の楽曲DBでは、下記画像の通り実際に _self というプロパティ名で、自分のDBにリレーションを張り、自分自身のページを参照しています。

プロセカNoteにおける楽曲DBでの _self relation の例

一見不要かつ無駄なプロパティに見えますが、この _self relation が効力を発揮するのは、Notion DB の Template を利用する時です。

Notion DB の Template でのフィルタの仕様

ここで話を Notion DB の Template 機能の方に向けてみます。
この機能は皆さん御存知の通り、DBのコンテンツやプロパティを指定したテンプレートを作り、ページ(今回はDBのコンテンツ)作成を容易にするツールです。

このテンプレート機能ですが、実はコンテンツ内に DB View のフィルターを指定する時、普通のページを作るときとは違った挙動をします。
下記画像のとおり、テンプレート内で作成したViewのフィルターでは、DB内の各ページだけではなくテンプレート自体を指定することができます。

テンプレートでのフィルターの挙動

もちろん、テンプレートを指定すると、現在閲覧・編集しているコンテンツが参照されます。 これにより、このテンプレートで作成されたページに於いて、容易にプロパティの入力をコンテンツ内で行うことが可能になります。
下記画像で補足すると、今までは赤枠のところでしかプロパティを編集できませんでしたが、DB Viewで_self relationのフィルターを使ってあげることで、コンテンツ内でも自身のプロパティを簡単に操作することができるようになります。

コンテンツ内でプロパティが編集が可能

改めて挙動をまとめると、

  1. テンプレートを使ってページを作る
  2. テンプレートが _self プロパティに対して、「自分自身を選択しているページを表示」しているViewを表示する
  3. _self に編集中のページを指定する
  4. コンテンツ内で、自分自身のプロパティを参照しているViewが表示される
  5. コンテンツ内でプロパティを編集できるようになる

という感じになります。

更に、このテンプレートの機能ですが、最近デフォルトテンプレートという、表示しているView毎もしくはDB毎にデフォルトで利用するテンプレートを設定することが可能になりました。
これで、まるで意識せずテンプレートを利用することが可能になりました。

_self relation と Template によって実現できること

コンテンツ内でプロパティを参照できるということはとても大きな意味を持ちます。

例えば、今回の課題の解決方法である入力フォームのグループ化も簡単に実現できます。

プロセカNote では画像のとおり、楽曲の基本的な情報を入力する Table View と 他の DB とのリレーションを指定する Table View をそれぞれ作ることで、擬似的に入力フォームをグループ化しています。

プロパティ入力フォームのグループ化

もちろん、この表示を入力フォームとして扱わず、プロパティをグループ化して見るために利用することも可能です。
同じくプロセカNote の例でご紹介すると画像のとおり、グループ化したいコンテンツで情報をまとめることが可能になります。

プロパティ表示のグループ化

以上を踏まえると

  1. プロパティをコンテンツ内でグループ化して入力できる(CUD)
  2. プロパティをコンテンツ内でグループ化して閲覧できる(R)

ということになります。

つまり、

ほぼすべてのプロパティを非表示にしてスッキリできる
ほぼすべてのプロパティを非表示にしてスッキリできる!!

またまた プロセカNote の画像例でお見せしましたが、これらの対応をすることで、ページ内でのプロパティ入力フォームをほぼすべて非表示にする事が可能になります。

学び

私がこの_self relationを見つけた時、周囲の反応は「ほぅ?」という感じであまり芳しくありませんでした。
今回、改めて私が本気で整備しているプロセカNote を参考にすることでこの有用性が伝わるのではないかと思っています。

想像力豊かな方はイメージが付いているかもしれませんが、この _self relationを活用すると、例えばタスク管理用のDBを作るときに、画像のようにEpicをコンテンツ内で確認したり、Epicの進捗を確認したり、Epic内に含まれるIssueの一覧を表示したり…....。
と汎用性は数限りなくあります。

_self relation の別の使い方
_self relation の別の使い方

今回ご紹介した _self relation はNotionのプロパティ操作における新たな表現のヒントになるのではないかと思います。

最後に

今回、私が運用しているプロセカNote を使って様々な Notion 運用のアイディアを提供できたかと思います。*15
これらのアイディアが皆さんの Notion ライフの一助になれば幸いです。

We Are Hiring!!

ABEJAでは共に働く仲間を募集しています。

今回は基本的にドキュメントツールであるNotionに関して色々ご紹介しましたが、Notionは現在弊社のオフィシャルのドキュメントツールとして活躍しています。 もちろん私のように趣味に全力を出しているメンバーもいますし、Notionを活用して業務をより効率化しようと工夫しているメンバーもいます。

ちょっと話を聞いてみたい、もう少し詳しく知りたいなどありましたら、是非ともご連絡ください。

データサイエンティスト以外にもソフトウェアエンジニアや様々な職種も募集しています。

心よりお待ちしております。

careers.abejainc.com

*1:業務とは全く関係なく開発しています

*2:Hajime の妄言と Tech の部屋: プロセカの Notion ページ作って公開しました!!

*3:正直、アクセス人数を増やしたいという下心はあります

*4:おそらく8万文字を超えるえげつないBlogになりかねないので...

*5:Google Japan Blog: Google Play ベスト オブ 2020 本日発表

*6:data.ai,2022年第2四半期の世界におけるモバイルゲームの消費支出は約2兆8835億円に到達する見込みであると発表より

*7:AppMedia:プロジェクトセカイ(プロセカ)攻略wiki:序盤の効率的な進め方【プロジェクトセカイ】より、画像参照

*8:GSSやMicrosoft EXCEL

*9:【Notion Chartsの使い方】データベースを超簡単にグラフ化【無料で5分で作成】 など

*10:もしかしたらこれより酷いDBをお持ちの方がいるかも知れません

*11: プロジェクトセカイ攻略Wiki:スコア判定について:スコア計算式を参考に設定

*12:本来ならば、この`ノーツ係数`と`フィーバー係数`が定数ではなく変数であり、これが楽曲ごとのポイントの差に大きく関わるハズなのですが、技術的に現状取得できていないので定数としています。

*13:入力できるすべてのプロパティに入力する場合

*14:Popin Sight: 徹底図解。入力フォームのデザイン・UXを高める15の方法: 15.関連情報ごとにグループ化をより画像のみ抜粋

*15:実はプロセカNoteの運用・開発に関してはこの4倍位書けますが、既に2万字を超えているので、「私は真に書くべきコンテンツをを持っているが、この余白はそれを書くには狭すぎる。」的な感じのあれで割愛します