近年流行ってる分報をタイムライン(TL)にまとめてみたよ

そもそも分報とは

どうも、タンバリンでクラウド開発してるナゴです。

分報とは、一言で言ってしまえば日報を分単位で行う社内ツイッターのようなものです。

times_*timeline_* 等のプレフィクスがメジャーですが弊社ではランダムの r_* で通っています。

suinさんや、ばんくしさんの記事が有名ですね

c16e.com

note.com

弊社でも分報制度を各自取り入れており、業務のことやプライベートなことまで多々書き込んでいます。

ぼくの分報 f:id:svu:20200313135507p:plain

分報の課題

先ほどあげた分報ですが、弊社ではアクティブユーザが40人(チャンネル)近くに増えてきており、各人が他の人の呟きをみる機会が減ってきたり、新入社員も増えてきたのでチャンネルに参加し辛い。。

このような課題が出てきました。

全ての分報を一つにまとめるチャンネルがあれば解決するのでは?

と思い立ち、実際に作ってみました。

Twitterのタイムライン(TL)をモチーフにしています。

使用技術

今回slackAPIを気軽に使える Bolt Framework(以後 bolt)を使用しました。 bolt はNodeJSのフレームワーク express のラッパーです。 去年ぐらいにオープンにされ、最近ではQiitaやstack overflowでも記事をみかけるようになった気がしてます。 また、ソースコードOSSで開発されています。

qiita.com

qiita.com

github.com

今回ホスティングは Heroku の有料 dyno にしていますが、

フリープランだと Heroku dyno が休止している期間があるため bolt の3秒レスポンスルールとは相性が悪いです。

課金するか ServerlessFramework や GAE などにあげた方が良いかもしれません

devcenter.heroku.com

ロジックとしてはとてもシンプルで以下のこれだけです。

  • slackApp(bot)を自分のチャンネルに招待する
  • その後、何か投稿するとTLに投稿がされる

また、レイアウトにはattachmentではなくblock kitを使用しています

api.slack.com

blockを使うことで attachmentの複雑なJSONがよりシンプルに記述出来るようになり、よりリッチな表現が出来るようになってます。

また block kit builder を使うことでGUIドラッグ&ドロップでレイアウトをいじることができ、同時にJSONも自動生成されるのでとても便利です

f:id:svu:20200313135734p:plain

また、こちらのライブラリを使うことでJSX風に記述することもできます

GitHub

github.com

リアルタイムプレビュー f:id:svu:20200313135955p:plain

できたもの

弊社のTL

f:id:svu:20200326191417p:plain
弊社のTL

slackApp名は TLけいさつ にしました

現在 betaですがTLの社内参加者や自分の分報に slackApp を入れてくれてる人も9割ほどいてるので嬉しい限りです。 今後も機能改善・追加をやってく予定です

また先日行われた Node学園関西では分報TLのネタで登壇してきました

資料

speakerdeck.com

おわりに

ぜひみなさん使ってみてください! そして瀬良さん、アドバイスありがとうございました :bow:

ソース

github.com

Backlogの課題をGoogleCalendarに連携してみた。

開発の経緯

運用案件で、CMSの構造上「STGに上げた記事は、本番には無条件に全部上がっちゃう」という案件がありました。

クライアントや各制作会社、開発している弊社がみんなでSTG環境を触っている案件ですので、「せめて弊社の分は記事の状態管理をしたいなー...」ということになり、「今STGに上がっているタスクが本番に上がるの(完了日)はいつだろう?」をGoogleCalendarに持っていくことにしました。

用意するもの

  • マシン
  • ブラウザ
  • Backlogプロジェクト
  • Googleアカウント

手順

1. 表示するGoogleCalendarつくる

作成してあるGoogleアカウントのカレンダーから 他のカレンダー の+マークを押して 新しいカレンダーを作成 します。

f:id:MATSUDAE:20200316120932p:plainf:id:MATSUDAE:20200316121013p:plain

連携するのに必要なカレンダーIDをメモしておきます。 f:id:MATSUDAE:20191111123058p:plain

カレンダー側は、一旦おいておきます。

2. BacklogのAPIキーを用意する

BacklogのAPIキーを用意します。

APIキーは、プロジェクト関係なく個人設定からするみたいで、まず迷いました。

本人アイコンを押したら、 APIを選択して、入力したらOK。f:id:MATSUDAE:20191111130337p:plain f:id:MATSUDAE:20191111130442p:plain

こちらも、取得できたら置いておきます。

3. GoogleAppsスクリプトエディタを用意する

Spreadsheetを用意して、スクリプトエディタを開くのが一番手っ取り早いです。

f:id:MATSUDAE:20200316123042p:plain

最近はclaspを使ってVSCodeで開発することも多いです。

その解説まで入れると長くなってしまうので、この解説では直でスクリプトエディタを開いてしまいましょう。

開いたSpreadsheetはlogでも残そうと思っててほったらかしにしちゃいました。あとで実装します。

4. まずはKey関連をセット。

スクリプトエディタにコードを書いていきます。

取得したkey関連をまずはセットします。

// 基本設定
var apiKey = 'xxxxxxxxxxxxxxxxxxxxxxxx',  // 上記2で取得したBacklogのAPIキー
  baseURL = 'https://xxx.backlog.com/api/v2/', //これはBacklog組織ごとに設定。バージョン固定
  taskBaseURL = 'https://xxx.backlog.com/view/';

// STGカレンダー設定
var stgCalendarID = 'xxxxxxxxxxx@group.calendar.google.com', // 上記1でつくったGoogleCalendarのID設定
    stgCalendar = CalendarApp.getCalendarById(stgCalendarID);

5. 必要な情報をset

プロジェクトの情報の中から、カスタム属性を取り出すのに必要な情報を取り出して、必要に応じて置いておきます。

// project情報
var projectName = 'xxxxx', // projectName
    projectURL = baseURL + 'projects/' + projectName + '?apiKey=' + apiKey, // projectデータのAPI
    projectJSON = UrlFetchApp.fetch(projectURL).getContentText(), // projectデータのJSONとってきた
    projectData = JSON.parse(projectJSON), // パースした
    projectID = projectData.id; // パースしたとこからprojectID取得

// custom属性
var projectCustomFieldsURL = baseURL + 'projects/' + projectID + '/customFields' + '?apiKey=' + apiKey,
    customFieldsJSON = UrlFetchApp.fetch(projectCustomFieldsURL).getContentText(),
    customFieldsData = JSON.parse(customFieldsJSON),
    statusFieldName = 'データの状態', // このカスタムフィールドを使用しています
    statusField,
    statusFieldId,
    statusTargetName = 'STG', // この値に合致するものを探してる
    statusTargetId,
    updateFieldName = '新規or更新', // イベントの内容に記入する項目のkey
    updateField;

for (var i in customFieldsData){
  if(customFieldsData[i].name === statusFieldName){ // 'データの状態' の入ってるとこみつける
    statusField = customFieldsData[i];
    statusFieldId = statusField.id;
  }
  if(customFieldsData[i].name === updateFieldName){ // '新規or更新' も同じように。
    updateField = customFieldsData[i];
  }
}

for(var i in statusField.items){
  if(statusField.items[i].name === statusTargetName){ // 'データの状態' の 'STG' を探す
    statusTargetId = statusField.items[i].id;
  }
}

6. 該当箇所のカレンダーをクリアする設定

上書きだけしていくとイベントの量がすごいことになるので、記入する前に前後1ヶ月のデータを削除することにします。

// 前後1ヶ月の日付を作成
function montlyData(){
  var today = new Date(),
      lastMonthDate = new Date(today.setMonth(today.getMonth() - 1)),
      nextMonthDate = new Date(today.setMonth(today.getMonth() + 2)), //TODO: ほんとは分けましょう。
      lastMonth = ('0' + (lastMonthDate.getMonth() + 1)).slice(-2),
      nextMonth = ('0' + (nextMonthDate.getMonth() + 1)).slice(-2),
      lastMonthDay = ('0' + lastMonthDate.getDate()).slice(-2),
      nextMonthDay = ('0' + nextMonthDate.getDate()).slice(-2),
      lastMonthDayText = lastMonthDate.getFullYear() + '-' + lastMonth + '-' + lastMonthDay,
      nextMonthDayText = nextMonthDate.getFullYear() + '-' + nextMonth + '-' + nextMonthDay;
  
  return {
    lastMonthDate: lastMonthDate,
    nextMonthDate: nextMonthDate,
    lastMonthDayText: lastMonthDayText,
    nextMonthDayText: nextMonthDayText
  }
}

// 前後1ヶ月のカレンダーをクリア
function clearCalendarData(taregetCalendar){
  var monthlyDataText = montlyData();
  var events = taregetCalendar.getEvents(monthlyDataText.lastMonthDate, monthlyDataText.nextMonthDate);
  for (var i in events) {
    var event = events[i];
    event.deleteEvent();
  }
}

7. 前後1ヶ月の課題を取り出す

プロジェクトの課題のうち、前後1ヶ月のものを取り出します。

// 課題
// (期限日が前後1ヶ月のもの)
function getIssues(){
  var projectIssuesURL = baseURL + 'issues?apiKey=' + apiKey + '&projectId[]=' + projectID + '&dueDateSince=' + montlyData().lastMonthDayText + '&dueDateUntil=' + montlyData().nextMonthDayText,
    projectIssuesJSON = UrlFetchApp.fetch(projectIssuesURL).getContentText(),
    projectIssuesData = JSON.parse(projectIssuesJSON);
  
  return projectIssuesData;
}

8. 課題をカレンダーにセット

取り出した課題渡してカレンダーのイベントとして登録する準備。

// 課題をカレンダーにセット
function setIssueToCalendar(issue, targetCalendar){
  var issueKey = issue.issueKey,
      summary = issue.summary,
      dueDate = new Date(issue.dueDate),
      url = taskBaseURL + issueKey,
      status = issue.status.name,
      customFields = issue.customFields,
      dataStatus,
      dataUpdate,
      descriptionText,
      options;
  
  for (var i in customFields) {
    if(customFields[i].name === statusFieldName && customFields[i].value !== null){
      dataStatus = customFields[i].value.name;
    }
    if(customFields[i].name === updateFieldName && customFields[i].value !== null){
      dataUpdate = customFields[i].value.name;
    }
  }
  descriptionText = issueKey +' '+ summary + '\n';
  descriptionText += 'タスクの状態: ' + status + '\n';
  descriptionText += 'url: ' + url + '\n';
  descriptionText += statusFieldName + ': ' + dataStatus + '\n';
  descriptionText += updateFieldName + ': ' + dataUpdate + '\n';
  
  options = {
    description: descriptionText
  }
  targetCalendar.createAllDayEvent(issueKey +' '+ summary , dueDate, options);
}

9. カレンダーに書き込む

全部準備ができたので、課題1件ずつをカレンダーに登録したら完了です。

// STG状態calendar書き込み
function writeStgCalendar(){
  clearCalendarData(stgCalendar);
  var stgIssuesData = getStgIssues();
  for (var i in stgIssuesData) {
    var issue = stgIssuesData[i];
    setIssueToCalendar(issue, stgCalendar);
  }
}

感想

  • こうしてみると全然簡単ですが、まず BacklogのAPIキーって、誰が発行権限もってるんだ? ってとこから始まりました。 -> Backlog入ってる人はだれでも発行できます!
  • ひとまず無制限に課題を引っ張り出してきたら、多分私の入っていないプロジェクトのデータまでずるっと引き出せてしまったので「あわわわ...」ってなりました。
  • Backlogで更新あったらカレンダーに...っていうのは更新あったのを取ることができなさそうなのでGAS側のトリガーで時間指定で設定しました。
  • この程度のことなららくちんだなー。Backlogのデータ、GASでいじるのはわりと楽な気がします。

「こういうのあったらいいなー」からAPIを調べて実装、までで1日ちょいくらいでできました。

BacklogのAPIは課題に関してはわりと使いやすいイメージです。「あともうちょっとコレがあれば...!」という機能を思いついたら、さくっと作っちゃうのがオススメですよ!

UIコンポーネント考 (1)

タンバリンの安部です。

Web開発やモバイル開発、UI開発を伴う多くの場面で「コンポーネント」という言葉を耳にするようになりました。 特にWebではWeb Componentsという技術もあり、多くの人がコンポーネントに関心を持っているのではないでしょうか。

では、そもそもコンポーネントとはなんでしょう?

再利用性がある?
独立している?
交換可能性がある?

Web分野のユーザインタフェースにおけるコンポーネント(以下、UIコンポーネントとする)はどのような概念として扱われるかを考えていきます。 今回はコンポーネントとは元々どういった概念を扱うのか、似た言葉や似た概念との比較を行います。 次回からはWebにおけるUIコンポーネントの考え方について、CSSやデザインツールの話を交えて考えていこうと思っています。

componentの語源と語義

componentは「構成要素」「部品」「成分」といったような意味があります。
語源としては com - pon - ent で分解され、それぞれ

分解した要素 語義
com 一緒に(類:company, collaborate, etc.)
pon 置く(類:deposit, post, etc.)
ent 人、もの(類:client, pendant, etc.)

となります。「一緒にまとめられたもの」といったニュアンスでしょうか。
詳しいことはこちらを参照してください。

英単語 component の語源と意味 - Gogengo! - 英単語は語源でたのしく

部品、という意味では日本語でも用いる「パーツ(parts)」という言葉があります。
part(s)はcomponentも含む「部品」という意味で、componentはその中でも「独立して動く部品」として分類されます。

オーディオ機器に「コンポ」というような言い方をしているのを聞いたことがあると思います。
これはオーディオステレオシステムの構築で、スピーカーやアンプ、CDプレーヤーなどの部品をそれぞれ購入して構成するコンポーネントステレオシステムの略語で、それぞれの部品は独立して動く製品で、それをいろんなメーカーや機種から選ぶことでシステムを構築する、といったものです。
スピーカーを構成する部品それぞれはオーディオシステムの構築時に独立して動作はしないため、「パーツ」として扱われ、スピーカーは「コンポーネント」として扱うことができます。

ある程度のスケールで構成された部品があるシステム上独立して/自己完結して動作するものがcomponent、というわけです。 日本語のニュアンスだと「構成部品」にはあまり自己完結性は感じられないのが難しいところですね。

componentとpartのニュアンスの違いについてはこちらもご覧ください。

component(コンポーネント)とpart(パーツ)の違い | ネイティブと英語について話したこと

moduleとの違い

module(モジュール)という言葉もよく使われますが、componentと比べてどのような違いがあるのでしょうか。
どちらもざっくりと「ある程度のかたまり」「部品の集まり」のような気がします。

moduleとの大きな違いは、「規格化されている」という部分です。
主には工業分野においてmoduleがそういう意味で扱われはじめたようですが、インターフェースが定められており、規格に沿っていればそのmoduleは交換可能である、というニュアンスがあります。componentはある程度の大きさで独立して扱える構成要素の部品群で、moduleは規格化されて交換可能な部品群であり、両者を満たすものもあれば満たさないものも考えられます。

ステレオコンポーネントシステムでいえば、スピーカーは接続部分のI/Oが同一であれば独立して扱えて規格化されて交換可能となります。これはmoduleでありcomponentです。ただ、独立して扱えないが規格化されている部品群はmoduleではあるがcomponentではないと言えます。
以下の記事が解説としてわかりやすいので合わせてご覧ください。

第29回 モジュール | 10分でわかるカタカナ語(三省堂編修所) | 三省堂 ことばのコラム

まとめ

  • componentはあるシステムにおいて、独立して動作する部品群である
  • partは部品全体を指す
  • moduleは規格化され、交換可能な部品群である

次回からはWebにおけるUIコンポーネントをどう定義するか、コンポーネントとそうでないものの区別の付け方、CSSやデザイン・JavaScriptフレームワークにおいてのコンポーネントの扱われ方などを考えていく予定です。

メモアプリをInkdropに変えた話

こんにちは。タンバリン大阪開発チームの北川です。 今回は普段使っているメモアプリをInkdropに変えた話をしたいと思います。

いろんなメモアプリがあり、なかなかほしい機能が揃っていないとか不満がありながらも使い続けていましたが、ようやく「これだ!」と思うアプリに出会えたので紹介していきたいと思います。

Inkdropとは

f:id:ykitagawa18:20200224180406p:plain

https://inkdrop.app/

Inkdropはマルチプラットフォームに対応したシンプルなMarkdownエディタです。

※有料で月額課金になります。詳細は以下のURLでご確認ください。

https://inkdrop.app/pricing

Inkdropを使うまでのメモアプリたち

過去にいろんなメモアプリを使ってきて、中には2年ほど使い続けたアプリもありました。 その中でも自分がメモアプリに求める機能をリストアップしてみました。

求める機能

  • Markdownが使える
  • Syntax Highlightがある
  • マルチOS対応で同期できる(Windows/Mac/iPhone/Android)
  • 記事ごとにディレクトリの作成ができる
  • 画像の管理をサービスに任せられる
  • テーマを変更できる
  • オフラインで使用可能

この中でも特に重要なのが、マルチOS特にスマホに対応していることと、画像をクラウドにアップロードできるということを重視していました。

移動中やふと何かを思いついたときのメモをスマホでも取りたいと思ったときにスマホアプリと同期できないという点が結構辛いなと感じてきたため、移行を決断しました。

Inkdropの良いところ

求める機能 実装されているか
Markdownが使える 使えます
Syntax Highlightがある あります
マルチOS対応で同期できる(Windows/Mac/iPhone/Android) 対応してます
記事ごとにディレクトリの作成ができる ノートブックを階層化できます
画像の管理をサービスに任せられる 10GBまで使えます
テーマを変更できる 変更できます
オフラインで使用可能 使用可能です

我ながら、ドンピシャなアプリと出会ったなというお気持ちです。

UIがシンプル

よく見るUIではありますがエンジニア向けに作られてるだけあって、UIは非常にシンプルで見やすいと思います。

f:id:ykitagawa18:20200224180427p:plain

記事のカテゴライズ

ノートブック・ステータス・タグで記事をカテゴリー分けできるので、記事の種類や状態(未完成など)を表現できるので、運用次第ではかなり使いやすくなりそうです。

私は基本的にノートブックを複数階層で作成して関連するタグをつけていく運用をしているのですが、記事の状態も忘れずに設定して記事の状態を可視化できるようにしたいですね。

プラグインで機能拡張ができる

プラグインで機能を拡張できるので、自分好みの機能を追加していけるのはいいですね。

f:id:ykitagawa18:20200224180422p:plain

テーマの追加

またUIテーマとシンタックステーマ別々で変更できるので、シンタックスをテキストエディタと同じにできたりするところもいいところだと思います。

f:id:ykitagawa18:20200224181246p:plain

記事単位でロールバックできる

記事ごとに履歴を管理しているのでロールバックすることができ、誤操作でテキストを削除してしまっても元のデータにリストアできるようになっています。

f:id:ykitagawa18:20200224180550p:plain

最後に

簡単に紹介しましたが、気になった方は60日間の無料トライアルがあるので、試してみてください。

https://inkdrop.app/

チームビルディングに効きそうな、「偏愛マップ」というワークショップをやってみた

f:id:matsuoshi:20200214163816p:plain

タンバリン松尾です。

最近、あるプロジェクトの立ち上げがありました。関わるメンバーは、社員・社外のパートナーさんを含め数十名。期間的にも弊社としては長めのもので、初対面という方も(パートナーさんを中心に)いらっしゃる状況。

  • 人数多い
  • 期間も長い
  • 初対面の方も何割かいる

という集まりです。

さて、そのプロジェクトのキックオフ・ミーティングで何かできないか、ちょっと考えまして。チームビルディングの助けになるようなことを、初期のうちにしておければなあ、と。

自己紹介のワークショップをやってみた

続きを読む

1on1を学びたいエンジニアのためのワークショップに参加してきました

株式会社Sansanさんで開催された、「1on1を学びたいエンジニアのためのワークショップ」に参加させてもらいました。

sansan.connpass.com

僕は普段は大阪開発チームのマネージャーとしてメンバーの1on1を実施している立場になります。ですので、なにかいい方法があるのか、や他の方がどのように行われているか、などを学びたいと思い参加させてもらいました。

ちなみに、参加されていた方の属性としては、マネージャーなどの1on1をする立場の人が半分くらい、残り半分はメンバーとして受けている立場の方、という感じだったと思います。

続きを読む