Open10

GitHub Actions のワークフローを開始する Google Drive のアドオン

hankei6kmhankei6km

アドオンの利用

もともとは Google Apps Script で Documents などのメニューにワークフロー開始コマンドを追加するつもりだったが、Makrdown ファイルだとできなさそう。

ドライブのコンテキストメニュー的なところに項目を追加できないかと調べてみたらアドオンを作るとサイドパネルが使えるもよう。

https://developers.google.com/apps-script/add-ons/overview

少し試してみたら Documents などの Google のアプリでなくてもファイルの id などが取得できたのでここれを利用する。

Google Drive 用のアドオンは以下が参考になった。

https://qiita.com/op71_kaz/items/653ce750fe75b80331c7

hankei6kmhankei6km

Input の内容を表示させない

入力の内容によってはログに表示させたくはない。

明確な方法は見つからなかったがイベントはファイルに保存されているので、そこから切り出すと表示されないようできた(と思う)。

https://stackoverflow.com/questions/67608874/github-actions-how-to-mask-workflow-dispatch-inputs-like-secrets

とりあえずファイルの保存をするだけなので、以下のようにしてある。::add-mask も問題ないと思われる。

on:
  workflow_dispatch:
    inputs:
      slides:
        type: string
        required: true
        description: Slide source


    steps:
      - uses: actions/checkout@v3

      - name: Parse event
        id: parse
        run: cat "${GITHUB_EVENT_PATH}" | jq -r .inputs.slides > slides.md
hankei6kmhankei6km

とりあえずの実装

https://github.com/hankei6km/test-drive-addon

実装して気が付いたところ。

ソースファイルの更新

  • clasp push の後、ドライブのウェブ UI をリロードすると更新は反映される
    • デプロイのやりなおしは不要
  • appscript.json の更新も同様

UI 的なところ

  • ボタンクリックで post などを行っているときはスピナーが表示される
  • Card を切り替えるとファイルを選択しなおしてもそのまま
    • 画面遷移をつめる必要がある

入力は

  • 複数行の入力も普通に扱える(ワークフローコマンド用の改行のエンコード的なものはなかった)
  • サイズ制限は不明(環境変数の大きさに左右されそうかな)

あとは

  • ワークフローの実行状況の確認
  • 複数同時に開始されたときに前のワークフローをキャンセル
    • ファイル ID が同じものに限定できる?

あたりは実施したい。

hankei6kmhankei6km

複数同時実行されたときの先行ワークフローのキャンセルについて。

並行処理グループには、任意の文字列または式を使用できます。 式は、secrets コンテキストを除く任意のコンテキストを使用できます。
https://docs.github.com/ja/actions/using-jobs/using-concurrency

file_id でグループ化したかったが、外部に出したくない情報でのグループ化は避けた方が無難なもよう。

対策としては secrets に salt 的なものを登録してハッシュ化するとか。
あるいは、割り切って全体で 1 つだけにするか。

hankei6kmhankei6km

concurrency.group の内容はキャンセルされた時に Annotations で表示される。

ANNOTATIONS
X Canceling since a higher priority waiting request for 'xxxxxxx' exists

add-mask が効かないかは試してない。

どちらにしろ危ないのでとりあえずハッシュ化用の処理を追加。

    steps:
      - name: Parse event
        id: parse
        run: |
          echo -n '::set-output name=group_key::' > cmd_out.txt
          envsubst < <(echo "$SALT") > key.txt
          jq < "${GITHUB_EVENT_PATH}" -r .inputs.file_id >> key.txt
          sha256sum < key.txt >> cmd_out.txt
          cat cmd_out.txt
          rm cmd_out.txt
        env:
          SALT: ${{ secrets.SALT }}

が、ハッシュ化の処理を記述した後に addon 側から正直 file_id を送信しなくてもよいことに気が付いた。addon 側で処理した方が楽だったかな。

hankei6kmhankei6km

addon のローカライズ

標準的な方法はない?

検索すると以下が定番ぽい。
https://vocal.email/blog/localise-google-workspace-addon

とりあえず。

type MsgKey_ =
  | 'SELECT_MARKDOWN_FILE'
  | 'NOT_SUPPORT_TYPE'
  | 'NOT_SUPPORT_MULTIPLE_SELECT'
  | 'ERR_WORKFLOW_NOT_STARTED'
const msgTbl_: Record<MsgKey_, Record<string, string>> = {
  SELECT_MARKDOWN_FILE: {
    ja: 'Markdown ファイルを選択してください。'
  },
  NOT_SUPPORT_TYPE: {
    ja: 'ファイルタイプは text/markdown のみサポートされています。'
  },
  NOT_SUPPORT_MULTIPLE_SELECT: {
    ja: '複数ファイルの選択はサポートされていません。'
  },
  ERR_WORKFLOW_NOT_STARTED: {
    ja: 'ワークフロー開始時にエラーが発生しました。'
  }
}

const msgLocale_ = Session.getActiveUserLocale()
function msgText_(key: keyof typeof msgTbl_): string {
  const msg = msgTbl_[key]
  if (typeof msg === 'object') {
    if (typeof msg[msgLocale_] === 'string') {
      return msg[msgLocale_]
    }
    if (typeof msg['ja'] === 'string') {
      return msg['ja']
    }
  }
  throw new Error(`Ivalid key: ${key} ${msgLocale_}`)
}
hankei6kmhankei6km

addon での入力

ユーザー入力のイベントの構造が実際の値とドキュメントの記述で異なる。
(型の定義はドキュメントと同じ)

https://developers.google.com/apps-script/add-ons/concepts/event-objects#common_event_object

少し検索したところ、以下が出てきた。変更があったということでよいのか?

Spoke to a colleague who figured out that the [""] is no longer needed. This is what works instead:

https://stackoverflow.com/questions/68811999/google-workspace-add-on-error-typeerror-cannot-read-property-stringinputs-of

hankei6kmhankei6km

関数にした。

function getSelectionInputValue_(
  e: GoogleAppsScript.Addons.EventObject,
  fieldName: string
): string[] {
  const targetValue =
    e.commonEventObject.formInputs?.[fieldName] !== undefined
      ? (e.commonEventObject.formInputs[fieldName] as any)
      : undefined
  if (targetValue) {
    if (targetValue['']) {
      return targetValue[''].stringInputs.value
    }
    return targetValue.stringInputs.value
  }
  return []
}