kintone Café 神奈川 Vol.7を開催しました



3/26にkintone Café 神奈川 Vol.7を開催しました。

開催の内容については、翌日に早くも渋屋さんがブログとしてアップしてくださいました。
https://100athlon.com/kintone-cafe-kanagawa-7/
松尾さんもアップしてくださいました。
https://note.com/matsuo_atsushi/n/nce32e0c0786b

私も渋屋さんと同じ感想ですね。松尾さんのセッションでFileMakerがkintoneの競合どころか、お互いの足りない部分を補い合える製品であることを知ったのは大収穫でした。23年前、私が初めて持ち帰りの仕事を受けたのはFileMakerを使った仕事だったというのに、全く状況を追えていなかった不明を恥じました。データ量や帳票などが整備されているのはさすが歴史のあるソフトウエアですね。
渋屋さんのセッションもテーマを深めるための冒頭の問題提議としてピッタリでした。最初にテーマに沿ったセッションを話してくださったことで、皆さんがkintone Caféの雰囲気になじめたと思っています。今回はzoomのチャットがとても盛り上がり、コメントも次々と書き連ねられたのですが、渋屋さんのセッションのおかげだと思っています。これは有効ですね。

LTは瀧村さん、松村さん、加藤さんと3人の方に登壇していただきました。
瀧村さんは、自己紹介をkintoneの基本機能と拡張機能を駆使した、まさに豪華絢爛kintone絵巻のような内容でした。わずかな間にkintoneの魅力を詰め込むところなど素晴らしい。エバンジェリストとして参考になりました。
松村さんは上海からご参加。kintoneを製造業に提案することの難しさと課題について素晴らしい問題提起をしてくださいました。中国のkintone事情にも触れたセッションに興味はつきませんでした。私も製造業へのkintone導入の提案については何度も跳ね返されています。このテーマで今後、セッションをお願いしたいくらいです。
加藤さんはkintone Café神奈川を立ち上げた方ですが、当日の朝にLTをお願いしたところ、快諾してくださいました。プラグインをあらためて紹介してくださったことで、あらためて浮き彫りになったのは、kintoneの標準機能の弱さです。まさに今回のテーマにふさわしく、kintoneでできること、できないことがプラグインからみえてきたように思います。
登壇してくださった皆様、ありがとうございました。

今回は、開催にあたっていくつか運用を変えました。最も大きい変更は、私自身が受け持つセッションをやめたことです。
今回、私は司会・進行に徹しました。セッションもLTにも登壇せず、冒頭の『kintone Caféとは?』を話したくらいで。
『kintone Caféとは?』では、あらためて過去のkintone Caféの開催状況を調べ直し、地図にして皆さんにお見せしました。地図を作りながら感じたのは、神奈川の開催回数の少なさです。

今まで私が携わったkintone Café 神奈川 Vol.2からVol.6までは、私が運営にも携わり、司会と進行を行うとともに、セッションも受け持っていました。
その体制のままだと、私が忙しくなると開催できず、開催間隔も延びます。
以前から神奈川の開催回数は少ないと感じていたのですが、今回、地図を作ることでそれを実感しました。それも私の忙しさのせいです。

そこで、前回のVol.6では、スタッフ体制を組みました。そのスタッフ体制を維持したまま、今回のVol.7を企画しました。企画にあたっても、最初から私は登壇もLTもしないと決めていました。
今回は、テーマも渋屋さんの発案です。

冒頭の『kintone Caféとは?』では、今後のkintone Café 神奈川は三カ月おきに開催する、と宣言しました。それもスタッフの皆さんのご協力があってこそ行えます。
次回以降、私は司会・進行すらしないかもしれません。

今回は懇親会の中でもスタッフを募りました。その結果、お二人の方が手を挙げてくださり、お一方はすでにやりとりもさせていただいています。また、当初、神奈川を立ち上げてくださった加藤さんにもあらためて入ってもらいました。
これでスタッフは私を含めて六人以上です。むやみに増やすのも考えものなので、これ以上募るかどうかは分かりません。ですが、次回以降は私がいなくても開催はできるのではないかと思います。

私の中で印象に残ったのは、懇親会で松田さんがおっしゃったひとことです。
今はオンラインなので地域別に分ける意味合いが薄れている。だから、主催者の色や個性が重要になるのではないか。とても良い意見で、参考になりました。ありがとうございます。
これからのkintone Café 神奈川も、これからは多様な色を出しつつも、どこかで私なりの特色を出しつつけてられるよう、考え続けていきたいと思います。

今回、ご参加してくださった皆様、誠にありがとうございました。


事例:北新海運様


kintoneを導入したことによる日常業務の効率化

  Topへ↓

北新海運様は、海運の国内運送を主に担っておられます。
海外の船がコンテナバースに到着し、荷揚げされたコンテナ。この荷物を国内のお客様に運ぶのが北新海運様の業務です。

従来はExcelで運用されていたお客様への請求業務や、ドライバーへの指示、ドライバーの稼働実績の取りまとめ。
それらをリリース間もないkintoneに切り替えることで、運用工数の削減を実現しました。

Excelによる単価管理やドライバーへの指示業務の煩雑化

  Topへ↑

お客様からのご発注を受け、それをドライバーへの運行指示書に印刷する。そして、その結果をお客様への請求書として発行する。ご発注から請求までの流れは単純に書くとこのような感じです。
ところが、お客様からの発送指示は千差万別。その発送場所によって単価が変わります。また、コンテナのフィートによっても単価が変わります。
配車指示を的確に行わねば、効率的な運用はできません。
また、案件ごとに単価が変わるので、その単価を的確に反映した請求書を発行し、同時にお客様によっては請求データを一覧表として発行しなければなりません。
そうした複雑な業務が伴う海運業務を行うには、Excelでは限界が生じていました。

また、ドライバーが従事した業務によって手当を計算する作業も負担になっていました。
複数の場所で荷を下ろす場合や、積み場所の変化。さらには、構内業務に従事する場合には別の計算方法が必要です。そして、それらが組み合わさった場合の作業もきちんと集計せねばなりません。そうした複雑な作業実績の集計は業務の負担となっていました。

リリースしたばかりのkintoneを提案し、採用。

  Topへ↑

Excelから離れ、新たなシステムを構築しなければ。
北新海運様で事務を統括する立場にあった紀村様は、旧知のデザイナー様に相談しました。
相談を受けた株式会社アトリエmの室井様は、そのご相談を受け、弊社にご相談くださいました。

その当時、まさにリリースされたのがクラウドサービス「cybozu.com」とそのサービスの一つであるkintoneでした。私はこのkintoneのβテスターであり、kintoneがリリースされたタイミングで導入できる機会を待っていました。そこにいただいたのが北新海運様のお話。
2012年の正月明けに北新海運様にお伺いし、紀村様にご要望を伺いました。
そしてご要望がkintoneで実現できると判断し、kintoneをご提案しました。

社長様は初めて会った私と、出来たばかりのサービスであるkintoneを信頼してくださいました。そして、ご発注にいたりました。
それが2012年1月26日のことです。

APIのリファレンスがなかった時期ゆえの苦労

  Topへ↑

私の手元には、この当時にサイボウズさんから頂いたMicrosoft Wordの「kintone API マニュアル」があります。表紙にはV0.1と書かれています。
この当時、リリースされたばかりのkintoneにはすでにAPIが実装されていました。とはいえ、レコードの追加・取得・更新・削除ぐらいしかできませんでした。今のリクエスト方法やレスポンス結果とそう仕組みは変わらないとはいえ、今のようなリファレンスは整っておらず、ウェブ上にブログなどの情報もほとんどありませんでした。
加えて、当時の私はRest APIを使った実装の経験がなく、ノウハウも持っていませんでした。

他にも問題はありました。それは、帳票をどうやって出すか、でした。
当時はまだプラグインの仕組みがありません。つまり、今のようなサードパーティーさんによる帳票発行プラグインもありません。
kintoneを使ってどうすれば帳票を出せるのか。まずその調査から始めました。

当時、すでに私は某案件でWeb上のphpライブラリ(TCPDF+FPDI)を使って帳票発行を実装した経験がありました。
そもそも、北新海運様からお話を伺った当初は、Accessによる実装という選択肢も有力でした。kintoneとAccessのどちらを採用するか決めかねていました。
Accessのレポートで帳票を出すのか、それともウェブ上でphpを使って帳票を出すのか。
私はそのどちらでもなく、Excelを使うことにしました。

VBA(Excelマクロ)からRest APIのリクエストを出し、その結果を受け取れれば、Excelで帳票が出せるのではないか。
私はそう判断し、ウェブ上であれこれと調べ、何とか実装にこぎつけられました。

結果、2012年の4月から運用を開始しました。ですが、当初は何度も調整が発生しました。
私自身、まだkintoneを完全に理解出来ておらず、ルックアップフィールドをはじめとしたフィールドの載せ替えなども何度も行いました。
夏前まではさまざまな調整が発生しましたし、帳票の発行についてもレイアウトや表示書式などなんども調整しました。おそらく全て落ち着くまでは1年はかかったように思います。

導入して9年

  Topへ↑

その後、当初の実装フェーズには含めていなかったドライバーの実績集計についても実装を終え、そちらも運用を開始しました。
さらに、北新海運様の業務拡張による別会社設立の際も、同じ仕組みの上に若干のカスタマイズを加えて実装し、運用を開始しました。

以来九年、Excelによる帳票発行の仕組みも含め、北新海運ともう一社の仕組みは動いています。

紀村様より

導入からの9年について、紀村様はこう語ってくださいました。

「アクアビット長井さんの原稿を拝見し、9年前の導入当時を思い出します。

弊社 有限会社北新海運は今年で創業17年目の会社になります。
9年前、私は入社3年目で事務全般を担当しておりました。その作業内容は受注・売上・請求だけではなく運賃管理・ドライバーの売上管理、それに伴う給料計算に至るまで殆ど全てでした。さらに車両の増車に伴う業務負担もあり現行をやり過ごすことが出来たとしても近い将来、必ず困った事態に陥る事は目に見えていたのです。

とはいえ当時リーマンショックからやっと立ち直ったばかりの小さな会社、そして海上コンテナ業界は輸出・輸入という特殊な世界のため専門ソフトがない中、システム導入を専門会社に依頼すれば何百万単位の金額が出てくる事は以前に在籍していた他社で見聞きし、十分に分かっていることでした。
コロナ禍における在宅勤務による環境の大きな変化でシステムが予算的にも身近になった昨今と違い、当時はシステムゼロからの依頼は予算の乏しい会社にとっては大きなハードルだったのです。

そこで広告・宣伝・HP作成等々の会社を経営なさっている友人にその問題点を相談をしたところ、アクアビットの長井さんをご紹介いただきました。
長井さんには何度も打ち合わせには来社、この業界の業務の流れをいちから丁寧に聞いてくださり、夜遅くなることもしばしばありました。

当時は長井さんが個人事業主ということもあり少し不安ではありましたが、kintoneを使ってのシステム構築でコストを非常に下げられるご提案を頂いたことで、初期投資として仮にリスクがあっても最小限にとどめられる事も想定し社長が決断して下さいました。
導入事例の中では1年程かかったとの文面がありましたが、kintoneの業務作業を並行・チェックをしながら手直しをし、追加リクエストもあったので、実は2年位はかかったと思います。その後、長井さんとのやり取りも年々少なくなってきましたが、それは業務システムが問題なく動いている証拠だと思います…いや、実はリクエストはまだまだありますが、kintoneバージョンアップがそこまでいっていないというのも本音です(笑)。

大手企業が作るシステムも大事ですが、実は中小企業の“小”の会社こそがこの様なシステムをすぐにでも必要としていると思います。初期投資が大きいために二の足を踏むオーナーさんも沢山いらっしゃると思います。そのなかには直ぐにでも使えるシステムが欲しい中高齢のアナログ世代の方々が相当いらっしゃいます。少なくともコンテナ輸送業界はそうです。
その様な会社を直ぐにフォロー出来るのがkintoneだと私は思います。
そして、この業界を少しでも知る長井さんの様なアドバイザー的な方がもっと増えることも今は大事かと考えます。

長井さんにはずっと以前よりkintoneの講習会参加を勧められておりましたが、今だ実現に至っておりません。コロナ禍収束の折には講習参加を真剣に考えたいと思っている今日この頃です。
有限会社北新海運 紀村多栄」

溝木様より

また、北新海運の溝木社長からはこのようなありがたいお言葉をいただきました。抜粋して掲載させていただきます。

「弊社も今年で創業16年になります。
長井様には色々なご相談に乗って頂き大変助かっております。
最初のころは、私はExcel、Wordも分からず、パソコンには色々苦労しました。

そこでお知り合いになったのが長井様で御座います。
弊社にとって何か良い業務方法ないか、何回も同じことを打つのではなく一回で作業を終わらす方法、当時、長井様は色々な方法を考えて下さいました、弊社の要望を聞きながら猛勉強をされたと思います。弊社の職業柄大変だったと思います。とても使いやすく助かっております。

専門的なことは今でも分かりませんが、長井様のおかげで業務が短縮になったのは確かです、継続は力なりとありますが、今では弊社にとって最高のパートナーです。
私がこれをやりたいとお話すると、私の考えが分かっているかのようにパソコンExcel、kintoneを組んでくれます。

昔の人は、「人は必要だから出会うと言います」必要でないのなら一生出会う
ことはないでしょう。
弊社もこれから色々なことに挑戦していきます、その度に長井様にお願いする
ことが多いと思います。
これからも末長く宜しくお願い致します。

有限会社北新海運 
代表取締役 溝木昭夫」

まとめ

涙が出そうになるほど、感動しました。
お二方より頂いた文章は、段落を整えた以外はなにも直していません。私と弊社にとってとても心強く、嬉しいお言葉です。

私も今までにいくつもの失敗をしてきました。特に、kintoneに出会う前、ホームページ制作を手掛けていた時期は。何度も。
でもこうしたお言葉をいただけると頑張ってきてよかったと思えますし、これからも頑張ろうと思えます。また、kintoneが私のスタイルに合っていたのだと強く思います。
北新海運の皆様、本当にありがとうございます。また、ご紹介を頂いたアトリエmの室井様、本当にありがとうございます。

北新海運様のご紹介

商号 有限会社 北新海運
本社 〒272-0522 千葉県市川市宮久保5-9-7
   
本社営業所 〒143-0001 東京都大田区東海5-4-1 大井海貨5号上屋北側1階
TEL 03-5755-7813
FAX 03-5755-7814
代表者 取締役 溝木 昭夫
従業員数 25名
設立 2005年(平成17年)9月28日
資本金 500万円
ウェブサイト http://www.hokushinkaiun.com/

kintoneが今年、拡がるために


はじめに

あけましておめでとうございます。
今年もよろしくお願いします。

弊社の年頭の抱負はこちらにアップした通りです。
その中で、kintoneを軸にするという項で、今年も弊社はkintoneに注力する旨を書きました。

そんな中、年始の2日に家族とワインを飲みながら団らんしていました。
テレビには家族が観たい「逃げるは恥だが役に立つ ガンバレ人類!新春スペシャル!! 」が映っていました。
そこに登場したテーマがコロナであり、選択式夫婦別姓であり、リモートワークであり、働き方でした。

私はドラマを観ながら、これってサイボウズさんの打ち出しとるテーマやんか、と目をみはりました。

昨年、弊社にとって顕著にkintone案件が増えました。また、旧システムからkintoneにシステムを移行したいとの案件のご依頼も増えました(ブログ)。
おそらく、その流れは今年も続いていくことでしょう。
それは、サイボウズさんの掲げる理念が世の中に広まったこととkintoneの魅力が世の中に行き渡りつつある証しだと思っています。
もともと、そうした働き方を変えたいとの機運は世に行き渡りつつありましたが、昨年のコロナはそれを促したのだと思っています。

ただ、kintoneはまだ世の中で誰もが知っている存在になっていません。
弊社のレベルでは顕著に案件が増えたとはいえ、それはもともと弊社が受けていた案件数が少なかっただけの話。
弊社の尺度ではなく、世の中全体でのkintoneのシェアを考えると、kintoneにはもっと広まる余地も拡がる伸びしろもあるはずです。
今年はその流れがより速まり、より拡がるはずです。より多様な業種や規模の企業にkintoneが採用されることでしょう。

ただ、それを口で予想するだけで済ませてはなりません。
自らが率先してその流れをより速め、広げていくことがkintoneエバンジェリストの役目であり、サイボウズ・オフィシャルパートナーとしての存在意義だと思っています。

そこで、kintoneに何が必要か。それを年頭に考えてみました。

kintoneの弱点

まず、kintoneに足りないのは、規模でしょうか。
今のところ、大規模案件や中小以上の規模の基幹システム案件に対して、kintoneを勧められる状態にはありません。
というのも、
https://jp.cybozu.help/k/ja/admin/limitation/limit.html
にも書かれているとおり、100万件以上のレコード件数に対しての品質保証が明記されていないからです。

過去データを保存する期間は、法令によると受発注や仕訳だと七年が定められているようです。
月間15000件の伝票が発生するお客様の場合、一年で180,000件。七年で1,260,000件のデータが発生します。
つまり法令上の保存レコード件数をkintoneが満たせないと見なされる可能性があります。

US版のKintoneは完全にAWSが基盤となったそうですから、オートスケールには対応しているはずです。

日本版のkintoneについても、今、サイボウズさんではNecoプロジェクトとManekiプロジェクトが進行中だそうです。


その成果を待ちたいと思います。

kintoneはもう一つ、アカウント管理回りにも改善の余地がありそうです。
例えば、大企業の複雑かつ大きな組織が一斉に組織改編を行う場合を考えてみましょう。周知のとおり、Cybozu.comのアカウント設定では組織の事前設定ができます。とはいえ、なかなか思った通りにいきません。ま、弊社はその部分の作業をお客様に委ねてしまっているのですが。
あと、ゲストスペース内アプリの権限設定の際に組織やグループが使えないのも不便だと思っています。

あと、スペースやレコード上のコメント機能も、ChatWorkやSlack並みの使いやすさをお客様から求められることがままあります。
kintoneの良さは、コミュニケーションとデータの融合にあると思うので、コミュニケーション機能のより一層の拡充は期待したいですね。

技術者としてすべきこと

さて、新年からクレクレマンのような要望を書いてしまいました。
とはいえ、昨年のkintoneに行われたバージョンアップの頻度は私たちの期待を満たしてくれています。
おそらく上に述べたようなことは私が言うまでもなく、サイボウズさんでも考えてくださっているはずです。
となると、こちらとしてはただ要望をいうだけではダメですよね。それだとクレクレマンに堕ちてしまう。
ユーザーや開発者の立場から広めるための働きかけを行わなければ。

例えば、kintoneのキャッチフレーズを考えるというのはどうでしょう。
kintoneを使えば何が良いのか。何が変わるのか。言葉を尽くしてそれを語るのはたやすいと思います。
ですが、一言でkintoneの良さを語るのは難しい。シンプルにズバリと本質をつく言葉を今年は考えたいですね。

また、ユーザーがシステムを作る手段としてkintoneの認知度は相当高まってきました。
また、年末にテレビCMが始まったことで、さらに認知度は上がっていくことでしょう。
とはいえ、技術者に対しての認知度はイマイチです。
さまざまなシステムを作る技術者の皆さんにこそ、kintoneの認知度を上げ、採用してもらわねば。

昨年、kintoneエバンジェリストとしてのインタビューでもその想いは語りました。
そこでも語った通り、わが国のシステム開発の生産性はまだまだ伸ばせる余地があると思います。統制のための統制、仕様のための仕様、ドキュメントのためのドキュメントではなく、設計から実装までの各フェーズが共通のフォーマットで流れるような仕組み。
海外のサービスの開発速度が速いのは、それができているからではないでしょうか。
わが国の場合、ミスが許されない文化性の違いもあるのでしょう。ですが、これからはバグや仕様を恐れない開発手法があってもよいと思います。
kintoneは画面や設計がプラットホームとして共通なので、共通言語で語れる部分も多く、スクラッチ開発よりもやりやすいはずです。

ただ、各アプリの連携やシステム全体の設計についてはkintoneは不得手ですよね。それを自動的に作れるようなツールが作りたいと常々考えています。各アプリを横断したER図や機能連携図を簡単に作れるようなツール。
これができれば、お客様との仕様確認も楽になるし、開発者側でもkintoneを導入する機運はさらに高まるはずです。それがわが国のシステム開発の生産性を上げられればいうことはありません。

弊社も昨年は、土壇場での仕様変更に何度も苦しみました。
ですが、その都度、kintoneの良さを生かしてすぐにリカバリできました。
今年はそのリカバリ手段をより研ぎ澄まし、要件定義に時間をかけずに、なおかつ、バグや仕様変更にもすぐに対応できるような体制を作りたいです。
それには、自社であらゆるパターンに対応できるJavaScriptやプラグインのストックをより多く作ることでしょうね。
その中で汎用的に出せそうなソースコードはブログなどで公開することで、エコシステムに貢献できればと考えています。

あと、今後は大手企業様の事例も増えるでしょう。
サブスクリプションの課金にも抵抗がないお客様には、積極的にサードパーティー製のプラグインを提案していこうと思います。
そのあたりは柔軟に取り組みたいですね。

あとは、ブログやYouTube、SNSなどの手段以外に、どうやって認知度を広めるかですよね。
kintoneにもともと興味を持ってくださっている方は、上記のようなメディアに来てくださいます。
ですが、世の中にはkintoneの存在すら知らない人がまだまだいらっしゃいます。そうした方にどうやって広めていくか。
おそらくkintoneのテレビCMはそうした意図で作成されたのだと思います。

あとは、私たちがどうテレビCMを補完するような発信ができるかですね。
おそらく私の場合は技術者向けのイベントや、SIerさんへの内部発信など、今まで取り組んできたことを深めていくのが良さそうです。
それと、弊社の場合、「自治会 IT」で検索すると一番上に登場しています(サイボウズさんの記事ですが)。

そのアドバンテージを生かし、今年は自治会やNPOにkintoneを告知するような手立てを考えていきたいと思います。
自治会やNPOといえば、比較的年配の方が活躍されています。年配の方は、ブログやYouTube、SNSに触れることも少ないように思います。そうした方へのアピールですね。
例えば市役所と組んだり、広報誌で告知したりといった手段で、実際に赴いてkintoneをアピールするのはどうか、と考えています。
おそらくそうした皆さんにkintoneを説明するには、システム用語を極限まで減らすなどの配慮が必要でしょう。

コロナで果たしてそうした機会がいただけるかどうかは不明です。が、チャレンジしてみたいと考えています。

kintoneが今年、拡がるためにはほかにも思いついた手段があれば試してみようと思います。


kintoneにシステム移したいんや


kintone Advent Calendar 2020の22日目の記事です。

  Topへ↓

おかげさまで今年はkintoneの案件が大幅に増え、ありがたい限りです。引き続き、頑張っていきまっせ^+++^

さて、案件が増えたのはめっちゃうれしいんやけど、今年は今までと比べ、案件の傾向に少し変化がありましてん。その傾向っちゅうのは旧システムからkintoneへ移行する案件の増加ですねん。
もちろん、昨年までも移行案件は請けてました。でも、今年はC/S(Cliend/Server)型の旧システムからkintoneへの移行案件をぎょうさんご依頼いただきまして。その数はめっちゃ増えましたわ。

おそらく今後もこうした移行案件は増えていくと思てますねん。そやさかい今年のkintone Advent Calendarは7回目の参加にして、初めて連携開発ではなく、移行について書いてみようと思とります。
これからkintoneへのシステム移行を手掛ける方の参考になればうれしいねん。

システム移行の建前と本音

  Topへ↑

システムを移行する目的はお客様によって千差万別です。
でも、システムを移行すると決断するのは経営層、少なくとも部署の責任者でっしゃろ?

この時、経営層と現場でシステムを扱う方の見る視点は違います。
経営層は人件費の削減や処理効率の改善に重きを置きます。一方で現場の視点は日々の業務の負担を軽減することと、業務を止めないことに向きがちです。

その時、建前上では業務改善を同時に行いながらシステム導入も行う、という意見が出ます。そりゃせやんなぁ。わてら提案側にとっても業務改善を錦の御旗に立てたほうが提案は通りやすいっちゅうもんで。

そやけど、往々にして現場の本音とは従来の業務フローを変えずに、なおかつ業務負担が軽くなることにあると思とります。業務改善によってオペレーションが大きく変わるのはいややなあ。新たなやり方になれるまでが大変やわ。てなわけで抵抗を覚えがちです。

わてがこの時に心がけとるんは、営業の相手である経営層や上層部とのお話がまとまったら、なるべく早いうちに現場の業務担当に挨拶し、現場の方とのコミュニケーションを増やし、現場の本音を伺いながら開発することですねん。
この時の建前と本音と取り違えると、あとあとまでボタンの掛け違いがえらいこっちゃになります。
また、営業目線でお客様の上層部とだけ話をしていると、現場のニーズが後から後から噴出してがっちゃがちゃになります。わては何度もこれでえらい目に遭っとります。

構築の順番

  Topへ↑

現場の方にとっては、慣れ親しんだ旧システムからkintoneという未知のシステムに触れるわけです。不安になって当然やんなぁ。
そやからわてはkintoneの良さ、つまり現場の方々が簡単にシステムが触れまっせ、簡単に設定ができまっせ、ということを早めにご説明します。

このあたりは旧来のシステム開発のセオリーとは違いますわな。要件定義、内部/外部設計、コーディング、単体テスト、結合テストが終わったあたりでようやくマニュアルが用意され、ユーザー受け入れテストで現場の方が触る。それが旧来のシステム開発。

わてはkintoneの場合はより早いうちに現場の方に触ってもらうことを重視します。フィールドコードは触らんといて、プラグインやJavaScriptはいじらんといて、という点だけお願いし、あとは積極的に使ってもらいます
その結果、kintoneってうちらでも触れるんや、現場でもシステム開発に参加してええんや、という実感を持ってもらえればもうしめたもんですわ。
この場合の現場というのは現場の統括者ではなく、文字通り日次の業務で手を動かしている方全員のことやからね。

これで現場の方からkintoneへの警戒感や拒否感が取り除けて、システム開発へ参加しているという実感を持ってもろたら、kintoneのファンにだってなってもらえます。そうなればシステム導入の成功は約束されたも同然ですねん。

その時、最後になるまでシステムを見せへん、という手法は逆効果。
まず移行が順調にできていることを示すためにも、一番やりやすいマスタ系からアプリを作っていくとよろしおま。
マスタは業務の基本であり、なおかつマスタアプリ自体には複雑なロジックはそれほど必要ないよって、導入側にとっても取り掛かりやすいはずですわ。
あと、早めにお客様にシステムに関わってもらうことによって、kintoneが苦手な部分をわかってもらうのも重要!なんでかいうたら、システム導入間近になっての仕様変更で断れるからやねん。

また、フィールドの追加や編集も自在にできるkintoneの特性がお客様に喜ばれるのもこの時。もちろん、ビジネスロジックに関わりの少ないフィールドを除いてやで。
例えば分析用に得意先種別を増やしたいねん、というご要望にもさくっと対応できるkintone。このかっこええ姿をアピールするだけで、好感度アップは間違いなしや。

また、ここで現場の方に新旧両方のシステムにマスタの入力を行ってもらえれば、次に述べるデータ移行の手間が大幅に減ります。ほんまやで。

データ移行の基本

  Topへ↑

kintoneへのシステム移行でいっちゃん肝心なんは、おそらくこの部分だと思とります。
kintoneはご存じの通り、簡単にアプリが構築できますやろ。そやから、元のシステムのデータの項目を再現するのはそれほど難しくない。そないに思うかたもおるんとちゃいますか?
ところが、ここを甘く見とったら後で苦労しますねん。

とくに、もともとのシステムの仕様で、マスタ上で管理する項目が制限されとったら要注意です。
入れたい情報を入れるべき項目がない。その場合、おうおうにしてお客様は備考欄にあれこれ詰め込みますねん。苦肉の策で本来ならメールアドレスやURLを入れるべき欄にまで雑多な情報を入れてしまいます。
kintoneでは簡単にフィールドを追加できまっしゃろ?でも、おうおうにして旧システムでは項目を追加するのに別途費用がかかります。だから、そないな状態になってしまうんですわ。
こうした雑多な情報がどこで管理されとるんか。その情報をkinotneのどの項目に移すんか。きちんと体系化されたデータとして活用したいんやったら、お客様ときちんと押さえとかなあきません。

次に、マスタをルックアップで呼び出す際の仕様は早めに定めとかなあきまへんで。御存じの通り、ルックアップでは関連付けるアプリを設定しますよって。
そしたら、コピー元のフィールドも設定せんならん。ルックアップフィールドではフィールドに文字を入力することで、候補を事前に絞り込むことができます。ただしコピー元のフィールドに設定した値に限りますけどな。

例えば得意先マスタの得意先コードを指定しておくと、マスタの得意先コードで検索が可能です。ですが、一度ルックアップフィールドをこさえた後にお客様から得意先名でも検索したい、と言われたらもうバンザイせなあきません。なんでかちゅうたら、コピー元のフィールドは一度設定すると変更でけへんよって。
もしどうしても変更の必要が生じたら、新たにルックアップフィールドを追加せんならんのです。もちろん古いルックアップのデータから新しいルックアップにデータを移さなあかん。そんなんいけずやんかぁ。

また、検索したい対象は得意先名だけに限りません。よくいわれるのは着信時の電話番号の末尾四桁ですわ。それ以外にもフリガナやらなんやら。つまり複数の項目で検索したいというご要望が出てきますねん。
この時は、わては検索キーっちゅう項目を設け、複数の項目の値を連結させとります。
この時はスペースなしでがっちゃんこしてまうとうまく検索がでけへんようになるから、半角スペースなどを間に挟んで連結するとよろしい。

さらに、その時は文字数にも気ぃ付けましょう。コピー元のフィールドに指定できるのは文字列(1行)です。
こちらのヘルプにも書かれとるけど、文字列(1行)の文字数に制限はあらへん。
ところが、今のページにはこうも書かれとったよね。
値の重複の禁止を設定すると、入力できる文字数が全角または半角で64文字までに制限されます、てな感じ。なんでやねん。

では、値の重複の禁止を設定せんかったらええんちゃうん?そう思いたなるやん?え?ならへん? いや、なってぇや。
なぜなら、ヘルプには以下のように書かとるからやねん。
既存のレコードを更新する場合、CSVファイルを読み込んで一括更新する方法が便利やと思うですわ。
けど、ヘルプにはこうも書かれとるねん。

こないな制限があったら、もうあかん。更新がでけへんのですわ。
APIで更新する場合も同じですわ。
ここのリファレンスにも。
システムの移行には、データの更新が欠かせませんわな。CSVで更新する場合も、APIで更新する場合も。
ルックアップ項目を更新するためには、値の重複の禁止せなあきません。ちゅうことは、連結した文字列の文字数が64文字を越えんようにせんならんのです。つまり、移行元のシステムの項目の文字数を考慮にいれなあかんちゅうわけですわ。難儀やなぁ。

もう一つ、移行にあたって注意しておかなあかんことがあります。
それは住所データの扱いです。日本の住所の場合、以下の4つの項目からなっとります。

  • 都道府県
  • 郡市町村
  • 住所(町・字・地番)
  • 建物・マンション名

これが旧システムでも四つに分かれとったら問題ないんよ。
そやけど、住所1、住所2という感じで二つだけのフィールドでしか管理されていない場合、えらいこっちゃになります。

なんでかゆうたら、それを分割する作業が発生するからですわ。分割?そんなんせんでええわ。てゆわれたかて、後々の分析のこと考えたらそうもゆわれへん。正味、お客様に後々の分析のことを考えてと提案してみたら、kintoneでは分割して管理するお客様が多いんですわ。
例えば旧システムの住所1に「東京都中央区日本橋2−7−1」。住所2に「東京日本橋タワー」の値が入っていたとしますやろ。そしたら、住所1から「東京都」「中央区」「日本橋2-7-1」を分割せんならんことになります。さて、ぼんやったらどないする?

わてはそんなとき、VBA(Excelマクロ)を活用しますねん。
その中でこちらのサイトを参考に正規表現から分割しますねん。
この正規表現をVBAの中に仕込んだら、都道府県と郡市町村と住所(町・字・地番)を分割できるんよ。
以下に簡単やけどコードを掲示してみたから、よかったらみてみて。必ず本番では使う前にテストしたってぇやぁ。もちろん当方では一切の責任は負われへんから。

Sub ConvertSplitAddressData()
    Dim regExp              As Object           '正規表現オブジェクト
    Dim strPattern          As String           '正規表現パターン
    Dim lngRowCounter       As Long             '行カウンター
    Dim intRegMatchCount    As Object           '結果
    Const clngMaxRowCount   As Long = 65535     '行の末尾
    Const cintTargetColumn  As Integer = 18     '結合された住所列番号
    Const cintPrefColumn    As Integer = 14     '結果の都道府県を格納する列番号
    Const cintCityColumn    As Integer = 15     '結果の郡市町村を格納する列番号
    Const cintAddressColumn As Integer = 16     '結果の住所を格納する列番号
    Const cintAnotherColumn As Integer = 17     '結果の建物を格納する列番号

    Set regExp = CreateObject("VBScript.RegExp")
    strPattern = "(...??[都道府県])((?:旭川|伊達|石狩|盛岡|奥州|田村|南相馬|那須塩原|東村山|武蔵村山|羽村|十日町|上越|富山|野々市|大町|蒲郡|四日市|姫路|大和郡山|廿日市|下松|岩国|田川|大村)市|.+?郡(?:玉村|大町|.+?)[町村]|.+?市.+?区|.+?[市区町村])(.+)"
    With regExp
        .Pattern = strPattern
        .IgnoreCase = True
        .Global = True
        For lngRowCounter = 2 To clngMaxRowCount
            If ActiveSheet.Cells(lngRowCounter, cintAddressColumn).Value = "" Then
                Set intRegMatchCount = .Execute(ActiveSheet.Cells(lngRowCounter, cintTargetColumn))
                If intRegMatchCount.Count > 0 Then
                    ActiveSheet.Cells(lngRowCounter, cintPrefColumn).Value = Trim(intRegMatchCount(0).SubMatches(0))
                    ActiveSheet.Cells(lngRowCounter, cintCityColumn).Value = Trim(intRegMatchCount(0).SubMatches(1))
                    If InStr(intRegMatchCount(0).SubMatches(2), " ") > 0 Then                '建物以降が全角空白で分割されている場合
                        ActiveSheet.Cells(lngRowCounter, cintAddressColumn).Value = Trim(Mid(intRegMatchCount(0).SubMatches(2), 1, InStr(intRegMatchCount(0).SubMatches(2), " ")))
                        ActiveSheet.Cells(lngRowCounter, cintAnotherColumn).Value = Trim(Mid(intRegMatchCount(0).SubMatches(2), InStr(intRegMatchCount(0).SubMatches(2), " ") + 1, 100))
                    ElseIf InStr(intRegMatchCount(0).SubMatches(2), " ") > 0 Then             '建物以降が半角空白で分割されている場合
                        ActiveSheet.Cells(lngRowCounter, cintAddressColumn).Value = Trim(Mid(intRegMatchCount(0).SubMatches(2), 1, InStr(intRegMatchCount(0).SubMatches(2), " ")))
                        ActiveSheet.Cells(lngRowCounter, cintAnotherColumn).Value = Trim(Mid(intRegMatchCount(0).SubMatches(2), InStr(intRegMatchCount(0).SubMatches(2), " ") + 1, 100))
                    Else                                                                      '建物がないか続けて入力されている場合
                        ActiveSheet.Cells(lngRowCounter, cintAddressColumn).Value = Trim(intRegMatchCount(0).SubMatches(2))
                    End If
                End If
            End If
        Next lngRowCounter
    End With
    Set intRegMatchCount = Nothing
    Set regExp = Nothing
End Sub

データ移行の方法

  Topへ↑

kintoneにデータを取り込む方法はいくつかあります。そやけど、だいたいはCSVによる一括登録・更新か、APIでの登録/更新かcli-kintoneの三択ちゃうやろか。
わてはCSV経由で取り込むことがほとんどですわ。
なんでかいうたら、APIやとHTTP Client Tool for kintoneを使う場合でも、何かのプログラムで書く場合でも、コードのマッピングの記述が面倒になるからやねん。
あと、一リクエストでも100件しか登録や更新ができないAPIの仕様制限があるやんか。そやから、100万件をこえるデータを移行する場合、一日のAPIリクエスト回数制限にも引っかかってしまうんや。
cli-kintoneは便利やねんけど、上と同じくマッピングの部分が手間なのであまり使用しとりません。ただ、余談すると、Bashとcli-kintoneを使った効率化は追及せなあかんと思とります。そやから弊社としてはcli-kintoneを使ったツールの作成ははよ進めよ思てます。
ただ、それでも画面上でマッピングを確認できるcsv取込の方が安全ちゃうかと思います。ここはよぉ考えてみてください。その時、旧システムが出すcsvの見出し項目名とkintoneのフィールドラベルは合わせといたほうが移行マッピングが圧倒的に楽になるで。

先に挙げた住所や備考以外にも、データの加工はあちこちのフィールドで発生しますやろ。それらはめっちゃ面倒やと思うねん。それらをチェックとか加工とかせなあかんから。例えば下のような感じやね。

  • 項目の値の中に半角カンマは入っていないか。
  • 項目の値の中にダブルクォーテーションなどは入っていないか。
  • 項目の値の前後に空白は入っていないか。
  • 旧システムから出力されたファイルの文字コードは統一されているか。(複数のシステムからそれぞれ出力して取り込む場合、Shift-JISとUTF-8が混在しているとkintone側で文字化けの恐れあり)
  • ルックアップの値はkintoneの関連付けるアプリの参照アプリのコピー元のフィールドに等しいか。
  • 電話番号や郵便番号、メールアドレスやURLの書式は正しいか。
  • 日付書式は正しいか。(元データがyyyymmdd形式の場合、空白セルがあると空白に変換してくれずエラーになる等)
  • データの重複はないか。
  • kintoneのラジオボタン/チェックボックス/複数選択/ドロップダウンの選択肢に等しい値のみが含まれているか。

もしでけるんやったら、VBAマクロやcli-kintoneなんかでツールを作ったほうがええと思うで。
あと、ツールの作成と簡単に書いとるけど、旧システムによって項目がまちまちなんは、わかりますやろ?旧システムごとに工数と時間を使うから、わては移行ツールの作成には消極的で、あまりやってへんかったわ。

そやけど、毎度Excelの関数を駆使して移行データを作るんはもうしんどい。わても実は今年手掛けた数々の移行の中で、一つだけめっちゃ苦労した移行がありましてん。それをしおに、めんどいかもしれんけど、移行ツールを作ったほうがええなぁと痛感しましたわ。

ただし、早めにマスタの項目を固め、マスタアプリを作り、初回のデータ移行がでけたところで、それ以降は本番までお客様にマスタデータの入力をしてくれまへんやろか?とお願いしたほうがよろし。もしそれがでけたらマスタの移行ツールはいらんからね。
ま、それでもトランザクションデータの移行に関しては何かしら作らんといかんけどね。

なんでかいうたら、マスタのデータは生き物やねん。お客様によってガーっと追加され、あちこちでバァーっと更新されますやろ。
しかも住所の更新が起こるし、しかも項目が連結されている住所データやったら、さっき書いたみたいに分割の作業が毎度いるんでっせ?
それに、本番移行直前でせーので一回でガバっと取り込めば済むほどシステム移行は甘いわけやあらへん。

もちろん、どっかの時点で初回分を取り込み、定期的に差分データを取り込み、最後に移行日までの残り差分がきれいに取り込めた場合は楽ですわ。そやけど、そうした移行の運用ができるのは、こっち側が旧システムにアクセスできる場合だけやねん。

遠方のお客様で、しかもVPNでつながれへんような旧システムの場合、データの取り出しはお客様にご依頼するほかありませんやろ。
お客様のやり方によっては項目に抜け漏れがあるやろし、項目の順番が違っていたりします。ましてや差分データの時間の基準もあいまいになってしまいますねん。そやから、きれいなデータを毎度もらえないと考えなあきません。
そやから、移行ツールを作らんでええ場合は、旧システムにこちらから簡単にアクセスできる場合に限ったほうがええね。もしそないにアクセス出来るんやったら、毎回Excelのフィルターや並び替えや置換などを駆使しても円滑に移行できると思うけどね。
ただ、実際はそうでない場合が多いから、そないなリスクを考えたら、最初に工数と時間をかけてでもお客様専用の移行ツールを作り、それを運用したほうが格段に楽やと思うんやけどどない?

データ移行のTIPS

  Topへ↑

なんぼかTIPSを列挙しておきますわ。

  • データの本番移行までは、取り込み時には変更履歴はオフにしといたほうがええで。
    設定は、アプリの設定→高度な設定→変更履歴のチェックを外す。
    そうしないとサイボウズさんに注意されまっさかい。また、変更履歴だけで契約のディスク使用量を軽く超過してしまいますねん。(一度、うっかりしていて100万件のデータを何度も取り込みなおし、サイボウズさんに注意されてもうた。※EvaCamp 2020で言いそびれた失敗談の一つ)
  • 可能であれば、本番移行後は旧システムのトランザクションデータ(売上や仕入などの伝票データ)は、kintoneでも別アプリにした方がええで。
    しかも、旧システムのトランザクションデータのフィールドはルックアップをなくし、文字列(1行)と数値のみにしといたほうが楽やで。なんでかいうたら、過去分のデータはマスタデータの変更などで古い値として入っとるからやねん。過去のルックアップはその時のマスタデータの値を再現して保存しといて、てな具合にご要望もろたら、移行作業はめっちゃごっつい苦行になってまう。さっきも書いたけど、ルックアップのコピー元のフィールドはマスタにある値やなかったらエラーになるからね。そやから、ここは初めのころに決めておいた方がええかも。
    旧システムの部分については別アプリに分け、ルックアップを外してしまえば、旧システムの生データをそのまま取り込むだけ済むさかい、ごっつい楽ですわ。
    そやけど、一つだけ注意しとかな。アプリを分けた場合、新旧両方のアプリをまたいだ分析に対応できませんやろ。その場合は、自分でカスタマイズビューを作成するか、トヨクモさんのDataCollectのようなプラグインを活用するとよろし。便利でっせー。
  • 添付ファイルの移行が必要な場合は先にも挙げたcli-kintoneを使うとええで。また、要件定義の段階でDropboxやboxなどのオンラインストレージの移行を提案しとったら、kintoneではそちらのストレージとの連携を行えばええから、あんじょういきまっせ。
    オンラインストレージへのデータ移行と整理作業は、お客様にお願いしといたほうがええやろし。
    2019年のわてのAdvent Calendarもご参考にしたってやー。
  • C/S側の旧システムは、たいがいレイアウトが小さくまとまっとるやろ。それに比べるとkintoneのフラットデザインは、項目の間の余白もがっつりとられとるし、そこは一目で情報が見られる旧システムに劣ると思われるかもしれへん。
    無理やりJavaScriptでレイアウトを調整したったりもしたけど、それはあんまり本筋のやり方ちゃうしなぁ。
    これも早い段階で現場の担当者と話を詰めといたほうがええで。
    あ、もう一個。デザインについてはkintoneのアプリの設定→デザインテーマの設定でブラックを選ぶとお客様の印象が変わるかもしれんで。一度試すとよろしおま。
  • Excel上でデータの加工を行う時、何使てます?フィルタ? もしそやったら、スライサーの機能とか試すと幸せになれるで。
    スライサーはこの記事が参考になるんちゃうかな。
  • kintoneの難儀な仕様ってあるやんか。例えば英単語の一部分の文字だけで検索でけへんとか。
    ヘルプにもこない書かれとるし。
    もしそれやったら、Excelのフィルタ使たらええねん。まあJavaScriptで
    一文字検索の機能を作ってもええし、プラグインでもええのんあるけどね。
  • C/S側の旧システムのユニークの項目は早めにお客様と共通認識をもっといたほうがええ。さっきも書いたけど、お客様からデータをもらう際は、旧システムのデータ出力方法を把握して、その設定や出力操作は完全に共通認識をもっといたほうがええ。あと、これも上で書いたけど、旧システムのデータを先に修正できるのならお客様に頼んで修正しといたほうがええ。

まとめ

Topへ↑

ちゅうわけで、本記事は皆様のkintoneへの移行のお役に、そしてご参考になればよろし思ぅて書きました。もちろん、これは弊社にとっても参考にすべき自社のノウハウですわ。
弊社も今年は一件、移行で苦労した案件がありましてん。だからこそ、一度ノウハウを言語化すべきやなぁと思ったんよ。それがきっかけです。
また、弊社の新規の案件の際にもお客様にも読んでもらお思ぅて、ここで知見を共有したいと思います。
なので、今後も折をみてアップデートを重ねていこうと思います。よろしゅうお願いいたします
また、こんなTipsや失敗談などありましたら、ガァーっとご意見をお寄せください。また本稿の内容に不備があった場合も遠慮せんとご指摘ください。

本稿が旧システムからkintoneへの移行をお考えの皆様にとってちょっとでも手助けになったら幸せやわ。


Cybozu OfficeのスケジュールをCalendar Plusで(だいぶ)再現!


カレンダーPlus Advent Calendar 2020の5日目の記事です。

  Topへ↓

先日のCybozu Days 2020 Osakaの後、Calendar PlusのAdvent Calendarに空きがあると聞き、やってみましょうか?と言ってしまいまして。
言った以上はエントリーせねば。というわけで翌日、神戸で商談する前に中華街で飯を食べながら記事にエントリー宣言。

さて、エントリーしたのはいいが、何を書けばええんやろ?しかもエントリーしたタイミングは前日やし(^^;)。これはひょっとして勇み足をやらかしてしまったのではなかろうか。

迷いを振り切った私が商談に臨むと、なんとお客様がCybozu Officeのスケジュールを愛用しており、それをkintoneに載せ替えたいとのこと。しかもkintone標準の標準のカレンダー機能は表現に乏しく、Calendar Plusを検討してみたけど、プラグイン設定の色設定の種類が少なくてなぁ。と悩みを吐露されまして。商談からネタが転がってきた瞬間です。
これはCybozu Officeのスケジュールアプリの画面です。

私は今までも何回かCalendar Plusは提案してきました。けれど、本格的なユーザーではないし、弊社のkintone納入実績からするとCalendar Plusの納品実績は一割もありません。熟練のユーザーではないのです。

が、Calendar Plus JavaScript APIを使ったカスタマイズの経験はあります。API使えば、ある程度ならCybozu Officeの体裁にCalendar Plusを合わせられるんちゃうの?という見切り発車でした。

Calendar Plusのプラグイン設定画面

  Topへ↑

うーむ、確かにCalendar Plusの色設定は12種類か。

そして、お客様のCybozu Officeのスケジュールの色設定で設けた種別は25種類。つまり倍。

確かにお客様がkintoneへの移行をためらわれるのもわかるなあ。

アプリを作る

  Topへ↑

じゃあ、ちょっと試しにアプリを作ってみよう。こんな感じで。

あらよっと、Cybozu Officeと同じようにレコードを登録してみました。さて、Calendar Plusで見るとどうなる・・・?

あかん・・・Calendar Plus側の色設定をしていないのですべて同じ緑地になってる。
しかも予定の状況がカレンダー上に表示されておらず、直行なのか直帰なのかが一目で区別できない。
やはり見にくいなあ・・・・これは、Calendar Plus JavaScript APIの出番かな。

コーダーモード発動

  Topへ↑

ということで、ガリガリとコーディングすること2時間。うん、こんな感じになりました。


というわけで最低限、記事にできるところまでこぎつけたのでいったん寝ます。アップ当日になってしまいましたが、AM2時前なのにもう眠い。年や。

プログラム開示

  Topへ↑
さて、アップ当日の朝、旅に出かける前に少しだけ記事を書きまして。
ここにコードを開示しておきます。すみません。洗練されたコードとは程遠くて・・・・。

(function () {
  "use strict"

  // 関数本体
  const calenderformat = function (event) {
    // Calendar Plusのイベントリスナー定義
    const calendareventslist = ["cp.event.show"];
    // kintoneのイベントの書き方と似ててわかりみ
    calendarplus.events.on(calendareventslist, function (calendarevent) {
      // kintoneのイベントのjson(上記イベントで対象データループしてくれます)
      const record = calendarevent.record;
      // kintoneのイベントの要素
      const element = calendarevent.element;
      // テーブルだけkintoneのイベントの要素
      const tdelement = document.getElementsByClassName("fc-event-container");
      // Cybozu Officeの時刻表示に合わせるため(ださいかも)
      const starttime =
        ("00" + new Date(record.開始時刻.value).getHours()).slice(-2) +
        ":" +
        ("00" + new Date(record.開始時刻.value).getMinutes()).slice(-2);
      const endtime =
        ("00" + new Date(record.終了時刻.value).getHours()).slice(-2) +
        ":" +
        ("00" + new Date(record.終了時刻.value).getMinutes()).slice(-2);
      // 左端の行高がCalendar Plusでは縮まるため、Cybozu Officeの行高っぽくする
      tdelement[0].style.minHeight = "90px";
      tdelement[1].style.minHeight = "90px";
      tdelement[2].style.minHeight = "90px";
      tdelement[3].style.minHeight = "90px";
      tdelement[4].style.minHeight = "90px";
      // リンクするためのurl
      const baseurl =
        kintone.api.url("/k/").replace(".json", "") +
        kintone.app.getId() +
        "/show#record=" +
        record.$id.value;
      // 予定の状況にあわせてSwitch(ださい)
      switch (record.予定の状況.value) {
        case "ーーー":
          // 枠の背景色
          element[0].style.backgroundColor = "#FFFFFF";
          // 文字色(本当はhoverとかの定義も必要。そもそもcssにまとめたほうがよい)
          element[0].style.color = "#006bac";
          // 折り返してくれなかったので折り返し(そもそもcssにまとめたほうがよい)
          element[0].style.whiteSpace = "break-spaces";

          // 終日フラグかどうか
          if (record.終日.value.length > 0) {
            // 頭に・をつける
            element[0].innerText = "・" + element[0].innerText;
          } else {
            // 頭に時刻をつけて改行
            element[0].innerHTML =
              starttime +
              "-" +
              endtime +
              "
 " +
              '<a href="' +
              baseurl +
              '">' +
              record.予定.value +
              "</a>";
          }
          break;
        case "直行":
          // 枠の背景色
          element[0].style.backgroundColor = "#ebf7ef";
          // 枠の線色
          element[0].style.borderColor = "#d8f2e0";
          // 文字色(本当はhoverとかの定義も必要。そもそもcssにまとめたほうがよい)
          element[0].style.color = "#006bac";
          // 折り返してくれなかったので折り返し(そもそもcssにまとめたほうがよい)
          element[0].style.whiteSpace = "break-spaces";
          // 終日フラグかどうか
          if (record.終日.value.length > 0) {
            // 頭に・をつけ、予定の状況を枠で囲む
            element[0].innerHTML =
              "・<span style="background-color:#32a759;color:#fff;" class="scheduleMarkEventMenu">" +
              record.予定の状況.value +
              "</span>" +
              record.予定.value;
          } else {
            // 頭に時刻をつけ、予定の状況を枠で囲む(以下コメントは省略)
            element[0].innerHTML =
              starttime +
              "-" +
              endtime +
              "
 " +
              '<a href="' +
              baseurl +
              '">' +
              '<span style="background-color:#32a759;color:#fff;" class="scheduleMarkEventMenu">' +
              record.予定の状況.value +
              "</span>" +
              record.予定.value +
              "</a>";
          }
          break;
        case "直帰":
        case "直行/直帰":
          element[0].style.backgroundColor = "#e8f2fc";
          element[0].style.borderColor = "#dae8f7";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#3182dc;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "転送":
        case "納品":
        case "設定":
        case "設置":
        case "移設":
          element[0].style.backgroundColor = "#f4fce8";
          element[0].style.borderColor = "#e5f2d3";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#83cb26;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "点検":
          element[0].style.backgroundColor = "#fff4e3";
          element[0].style.borderColor = "#fae8cd";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#ef9201;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "デモ":
        case "営業":
        case "商談":
        case "打合せ":
        case "来客":
          element[0].style.backgroundColor = "#faf6f2";
          element[0].style.borderColor = "#f2e9df";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#c3a88b;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "当番":
          element[0].style.backgroundColor = "#fcfae8";
          element[0].style.borderColor = "#f2efd3";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#dfc506;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "休み":
        case "有休":
        case "リフ":
        case "代休":
          element[0].style.backgroundColor = "#ffebeb";
          element[0].style.borderColor = "#fcdede";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f44848;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "午前半休":
        case "午後半休":
          element[0].style.backgroundColor = "#fff2f5";
          element[0].style.borderColor = "#f7e9d2";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f3a4b4;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "土曜当番出勤":
        case "出張":
        case "会議":
          element[0].style.backgroundColor = "#f5edfc";
          element[0].style.borderColor = "#ebe1f5";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#b592d8;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
        case "土曜振休":
          element[0].style.backgroundColor = "#ffebeb";
          element[0].style.borderColor = "#fcdede";
          element[0].style.color = "#006bac";
          element[0].style.whiteSpace = "break-spaces";
          element[0].innerHTML =
            "・<span style="background-color:#f44848;color:#fff;" class="scheduleMarkEventMenu">" +
            record.予定の状況.value +
            "</span>" +
            record.予定.value;
          break;
      }
      return calendarevent;
    });
  };
  // kintone自体のイベント
  const eventslist = ["app.record.index.show"];
  kintone.events.on(eventslist, (event) => {
    // ビューが一致したら本体の関数呼び出し
    if (event.viewId === 5543487) {
      calenderformat(event);
    }
    return event;
  });
})(jQuery);

あと、cssも全体のフォントに関する設定と予定の状況の枠に対して設定しています。

div#calendarPlus {
    font-family:"ヒラギノ角ゴ Pro W3", "Hiragino Kaku Gothic Pro", ヒラギノ角ゴシック, "Hiragino Sans", メイリオ, Meiryo, "MS Pゴシック", "MS PGothic", sans-serif;
    font-size:14.4px;
    font-weight:400;
}
    
div#calendarPlus span.scheduleMarkEventMenu{
    display: inline-block;
    padding: 1px 2px;
    color: #fff;
    margin: 0 1px 2px 1px;
    line-height: 1.2em;
    border-radius: 2px;
    font-size: 12px;
    vertical-align: middle;
}
    

まとめ

  Topへ↑

てなわけで、日中は記事のことは一切忘れて徳島のあちこちをめぐっておりました。で、21時前に実家に帰ってきました。
では、早速記事の続きを。うん、あらためて確認しても表示はこんな感じで大丈夫かと。

本当は実装すべきなのは週だけでなく、他の日や月でも同じように表示できるか試さねばならないですね。また、Cybozu Officeのスケジュールアプリの日時と曜日の行見出しの部分はCalendar Plusでは再現していません。
まあこんな感じですが、記事の主旨は満たせたと判断し、今宵はここまでにしてアップしたいと思います。

あとは、金曜日に商談したお客様がこの実装でご満足いただけるかどうかでしょうか。なにしろ、お客様に商談の御礼のメールを送るより先に、こちらの記事をアップしてしまったので(^^;)。おいおい。

本稿がCybozu Officeからkintoneへの移行をお考えの皆様にとって少しでも手助けになれば幸せです。

もしお困りの際は弊社までご連絡くださいませ。

当エントリーの参考にさせていただいたブログ

  Topへ↑

最後になりましたが、このエントリー作成にあたり、以下のサイトからの情報を参考にさせていただきました。
ラジカルブリッジの斎藤さんにはいつも感謝です。内容にやばい内容が含まれていたら直しますね。
ありがとうございました。

 Calendar Plus JavaScript APIのリファレンス


RPA界の技術者の皆様にお話しをしました



今月、唯一のお話の機会をいただきました。
お招きくださったのはPeaceful Morning株式会社様です。

Peaceful Morning株式会社様は、RPAの界隈のメディアを運営し発信しておられると同時に、RPAに知見のある大勢の技術者を擁し、RPAのツールやサービスを提供されています。
Peaceful Morningという社名にも見られる通り、従来の働き方に一石を投じる理念は、私が推すkintoneやサイボウズ社にも通ずるところがあります。
https://peaceful-morning.com/

今回は、一時間程度のお時間をいただきました。
一時間のすべてを座学形式ではなく、技術者の皆様から事前に質問を募り、それに答える構成でした。

事前にいただいた皆さんの質問内容を拝見すると、kintoneとは何かを知りたいというのは当然として、機能の詳細よりもむしろkintoneの概要や競合ツール、さらにコロナ禍におけるkintoneの需要や今後の展望など、kintoneを扱うことの可能性にご興味がおありの様子でした。
また、kintoneを扱うことによる働き方の変化や、エバンジェリストである私自身がkintoneを推す理由など、技術者としてのキャリアの可能性の一つとしてkintoneを検討したいとの意識を感じました。

事前の質問だけでも十二問に達し、質問に答えるだけで一時間が過ぎることは確実でした。
そのため、kintoneエバンジェリストとして登壇する私は、kintoneそのものの機能よりも、私の個人的な思いや、kintoneのどこに可能性があるのかといった本質的な部分を語ろうと決めました。

折悪しく本番直前になって私のPCの処理が終わらず、kintoneの画面イメージが映せなかったため、kintoneの紹介すらも口頭のみで済ませてしまいましたが(終わりの方で処理が終わったので画面共有で3,4分ほどお見せできました)。

私からの回答の中では、kintoneはREST APIが使えればほかのシステムとも連動が可能とお伝えしました。RPAでいうとUiPathはREST APIが扱えるので、kintoneとは相性が良いですよ、ということも。
おそらくRPA界の皆様には、データベースのバック/フロント構築が簡単にできるツールですよ、という視点で深く掘り下げたほうがより響いたのかもしれません。

参加者の皆様からのアンケートも開示していただきました。
kintoneの画面の動きやRPAとの連動の実例をもっと見たかったというご意見もありました。当然ですね。私の不手際があり、失礼いたしました。
また、私のkintone愛を感じた、といったご意見も数点ほどいただきました。それはまさに狙い通りのご感想だったのでうれしかったです。これを機会にkintoneに興味を持ってもらえればなおさらうれしいです。
RPAとkintoneの連携事例はまだ世に出ていないと思っているので、皆さんと一緒に事例を増やしていきたいです。

こうやって分野の違う技術者の方を前に語るといつも感じることがあります。
それは、kintoneのすそ野がユーザー部門には広がっているとはいえ、まだまだ技術者への浸透には至っていないということです。
技術者の中にはスクラッチ開発に価値を感じる方もいらっしゃれば、基盤から構築してすべてを統制することに意義を認めていらっしゃる方もいます。
もちろん、それは人それぞれなので自由です。ですが、No-Code/Law Codeといった従来のシステム開発の概念と違うツールへの使わず嫌いがあるとすればもったいないと思うのです。

ここ一年、私の中でエバンジェリストとしての活動の主眼の一つを技術者さんへの伝道に置こうと決めました。
それは、私がkintoneに賭けようと思った時期を見つめなおしたことで気づいたことです。
その時期、私には常駐現場を渡り歩く中で感じた既存のシステム開発への疑問が渦巻いていました。それは、人月換算や多層契約によるSES業務の課題、膨大なドキュメントの山、そして作った設計書や仕様書やプログラムが現場のオペレーターさんに直接届かないもどかしさなどほんの一部でしかなく、とても悶々としていました。
その時期の私がkintoneに感じた可能性とそれに賭けようとしたことで、今の私はあります。
それを考えると、技術者の方々が今の開発環境やツールから別のステップに進みたいと感じたときに、kintoneを開発ツールの一つとして選択肢に含めてもらえれば、私がkintoneに身を投じたモチベーションも共有できると考えたのです。

RPA界の技術者の皆様には、また機会があればkintoneを中心としたPaaS/SaaSの可能性について語ってみたいですね。今度はきちんと画面の動きもお見せしながら。今回の処理の遅れを機会にノートPCを新調したことですし。

まずは、今回のご参加者の皆様、ありがとうございました。
また、セミナーを運営してくださったPeaceful Morning株式会社の皆様、ありがとうございました。


freee & kintone BizTech Hackの第一期を終えて


9/17に、freee & kintone BizTech Hackの第四回目が行われました。
(freeeさんからのレポート)。
私は久々にオンラインハンズオン講師を務めました。第一回目以来です。

4/24に行われた第一回で感じた感想は、以下の記事に書きました。
https://www.akvabit.jp/freee-kintone-biztech-hackでオンラインハンズオンの講師を務めました

第二回と第三回では、ハンズオンのメンターとしてサポート役を務めさせていただきました。また、ビジネスセッションの登壇者として、SaaSの隆盛について、基幹システムの最後の砦である会計にもクラウドが使われるようになってきた現状を語りました。

こうしたオンラインハンズオンでの登壇経験や、メンターとしての参加は、私の経験値をかなり高めてくれました。おそらくは知名度も。
そうした活動をSNSに載せる中で、何人かのお客様には「kintone以外にもfreeeにも詳しいんだよね?」という嬉しいお言葉まで。

何よりも私が持っていた経理業務への苦手意識が、この四回の登壇を通して払拭できたように思います。

おそらくはfreeeの設計思想が、貸方借方に象徴される伝統的な簿記ではなく、トランザクションデータとしての受発注伝票に似た形式で組み立てられていたことが大きいと思います。

なんや、普通のデータのように扱えるんや、という安心。
これが学べたのは私にとっても大きいですね。

また、APIキーではなく、きっちりとしたOAuthの仕組みに則った認証が求められることによって、私の知見を高めてくれました。ハンズオンをやる以上、理解していなければ。
これはかけがえのない財産になってくれそうです。

今も、とあるkintoneとfreeeの連携案件で、kintoneから投げたwebhookを、freee側でリフレッシュトークンを使い、都度の認証なしに連動する仕組みがを作っています。その時ももOAuthの知見が生かされました。

四回目のハンズオンでは、参加者の方から、今後のハンズオンはこういうのを期待している、と言った前向きな意見をいただきました。
四回目にして初めてこうしたご意見が頂けたのは嬉しいです。講師冥利に尽きます。
今回で第一期は終わりますが、次回に繋げたいですよね。
他にもお褒めのお言葉をいろいろといただけたことですし。

実は今回の登壇は、別案件をいくつも並行ですすめている中の登壇でした。かなり疲労が溜まった中での登壇でした。でも、オンラインの恩恵を受け、無事にやり遂げられました。疲れてても喋りではなんとかやり遂げられる自信がついたのも大きいです。

おそらくはまだ、これからも来年も、オンラインの上のイベントが主流になると思います。
オンラインハンズオンという挑戦が無事に終わったことで、私の自信になりましたし、私も次なるチャレンジに挑んでみたくなりました。
例えば、先日作った私のアバターを出してみたり。

最後になりましたが、ご参加者の皆さん、メンターやハンズオンを行っていただいた皆さん、ありがとうございました。


弊社はCYBOZU DAYS 2020に出展いたします。


9月が始まった今日、
弊社にとって三つのニュースを発表できる機会が到来しました。

一つ目は、
CYBOZU DAYS 2020への出展です。
2020年11月に千葉の幕張メッセで開催されるCYBOZU DAYS 2020のシルバースポンサーとしてブースを出展いたします。
弊社とPolaris Infotech株式会社(https://www.polarit.co/)との共同出展の形をとらせて頂きます。

URL:
https://cybozuconf.com/
日時:
2020/11/11-2020/11/13
場所:
幕張メッセ 国際展示場1-3ホール
(弊社の出展ブースの場所はすでに決まっていますが、詳細は当日お越しになられた際に配布される地図をご覧くださいませ)

来場方法:
JR京葉線 – 海浜幕張駅 (東京駅から約30分、蘇我駅から約12分)から徒歩約5分
JR総武線・京成線 – 幕張本郷駅(秋葉原駅から約40分)から「幕張メッセ中央」行きバスで、約17分
出展内容:
お楽しみに

Cybozu Daysは、毎年大勢のお客様がお越しになります。今年はコロナ禍のため、感染対策の中で開催されます。
若干の制約事項もあるでしょうが、来場された方は必ず楽しめますし、持ち帰って糧としていただけるだけの内容になっています。これは毎年参加している代表が心から実感していることです。
是非、来場のお申込みをお待ちしております。

二つ目は、
弊社の代表がインタビューを受けた記事がkintone エバンジェリストのサイトで公開されました。
https://www.kintone-eva.com/
代表は今までも何度かインタビューされた経験や、記事に登場したことがあります。
ですが、今回のインタビューは、kintoneを通した代表自身の生き方にまで踏み込みましたので、とても思い入れがあります。

こちらもお読みいただければと思います。

最後は、
弊社のロゴの刷新です。
akbを組み合わせた従来のロゴの色合いをkintoneに近づけたものです。
このデザインは弊社代表の妻と長女によるものです。

長女も現在、デザイン専門学校生ですが、弊社の社名のakbの文字を組み合わせつつ、同時に弊社の代表の頭文字「N」を表したデザインは、デザインやプレゼンテーションのプロの方からもおほめ頂きました。

よりkintoneにデザインを近づけたことで、kintoneに賭けようとする弊社の意気込みを感じて頂ければと思います。

まだ発表していませんが、この夏には
・社是
・企業理念
・経営理念
・9つ(ナイン)のない
・アクアビットに合わない方
という五つの指針も作成しています。

また機会があればご紹介したいと思います。

今後ともよろしくお願いいたします。


freee & kintone BizTech Hackでオンラインハンズオンの講師を務めました


4/24(金)に行われた「freee & kintone BizTech hack」で、セミナーとハンズオンを弊社代表が担当させていただきました。
今回、初めての開催でありながら、全てがzoom上で行われました。100%ピュアのオンラインです。

ハンズオンとは講師が壇上に立ち、スクリーンに映した作業の一部始終を参加者に見てもらいながら、参加者にも同じ操作を再現してもらうセミナーの形態です。

ハンズオンは、どこか一つでも参加者が手順を誤るとうまく動きません。ですから通常は数名のメンターやサポートスタッフが会場を巡回し、参加者が課題を完遂できる様に導きます。
それでも多くの方が途中で脱落してしまいます。
私も参加したハンズオンのうち、完遂できなかったことがあります。

今回はそのハンズオンをオンライン上で行いました。つまり、難しさはさらに上がります。

弊社代表は、参加者を会場に集めてのハンズオン講師の経験は持っていましたが、オンラインハンズオンの講師は初めてです。
初めてだったのは弊社代表だけではありません。運営側の全員がほぼ未経験でした。
そのため、リハーサルは三回行いました。

皆さんにどうやったらわかりやすく内容を伝えられるか。どうやれば皆さんにハンズオンの課題を完遂してもらえるか。
手順を省くことは許されません。参加者がどこまで理解しているかも予断できません。

矛盾のない内容になっているか。話す速度は大丈夫か。提供するプログラムは破綻していないか。
リハーサルではそうした確認を行い、運営者の皆さんから指摘してもらいました。KPT法にのっとり、keep、problem、tryを繰り返しつつ。

結果として、15名の参加者の皆さんに最後まで完遂してもらうことができました。これも運営の皆さんと参加者の皆さんのご協力のたまものです。ありがとうございました。
公式開催報告:(https://fk-biztech.qloba.com/activities/7256

私にとって、今回の経験はとても得難いものでした。
ハンズオンの講師は、内容を完全に理解していなければ務まりません。
今回、ハンズオンでOAuthの仕組みを扱うにあたり、今まで何となく扱っていたOAuthについてさらに理解を深めました。
学びを通して自分の中にOAuthの仕組みをきちんと落とし込めたことは、今後の業務にとって助けとなるはずです。

ハンズオンの前のセミナーでお話しした通り、これからはSaaS同士の連携が欠かせません。
セミナー資料:(https://slides.com/yoshikazunagai/freee-kintone-biztech-hack-seminar
既存の構成から、新たな仕組みへと。
技術者に求められる役割はこれからも多岐にわたることでしょう。私たち技術者が学ぶべきことは多く、しかも新たな技術を取り入れねばなりません。

今後もオンラインでハンズオン講師を務める機会はあるでしょう。
私もできる限り、力を尽くしたいと思います。
より多くの人にエコシステムを使ってもらうために。より多くの技術者にオープンプラットホームの世界で活躍してもらうために。

すでに今回、ご好評をいただいたことで、次回の開催が決まっています。
freee & kintone BizTech Hackの二回目が5/22に行われます。
申込サイト:(https://fk-biztech.qloba.com/
次回は私は登壇しませんが、スタッフとして皆さんをサポートさせてもらえればと思います。
よろしくお願いします。


kintone Café 神奈川 Vol.6を開催して得た気づき


4/2にkintone Café 神奈川 Vol.6を開催しました。

公式の開催報告はしかるべき場所に書かせていただきました。
こちら

関連するツイートのまとめサイトも作成させていただきました。
Twitterはこちら
zoomグループチャットはこちら

代表である私が登壇の際に語った
kintoneの導入はお客様にファンになってもらいながら!
サイボウズ社のチーム応援ライセンスについて
については、上に挙げた開催報告の中で補足させていただいております。

ここでは、完全オンラインで開催することで気づいたことを書きたいと思います。

まず、開催にあたって大変お世話になったkintone大好きYouTuberこと、サイボウズ社の松井さんにはお礼を申し上げなければ。
松井さんからは、zoomでの運用のノウハウや、YouTube Live配信のノウハウを教えていただきました。当日も開催の間、さまざまなフォローを行ってくださいました。
最終的に、リアル開催からオンライン開催に切り替える決断をしたのも松井さんの一言がダメ押しとなりました。色々とありがとうございました。

あと、当日はYouTube Liveで同時配信を行いました。
配信された結果の動画は、松田さんが編集を買って出て下さいました。助かりました。ありがとうございます。
その動画リストはこちらに載せてくださっているようです。

さて、今回は私にとってはじめてのオンライン・イベントの主催でした。
リアルのイベントでは何度か主催を経験してきましたが、オンライン・イベントは初めての経験。同じ技術イベントとはいえ、やるべき作業は違います。

一言でいうと、事前と最中の忙しさの違い、でしょうか。
リアルのイベントでは事前の準備が大変ですが、開催の間はそれほどやることはありません。
一方、オンラインのイベントは事前の準備より、開催中のほうが大変です。
その部分をお知らせできれば。

オンライン・イベントの場合、事前にやるべき事は大きく分けて三つあります。
一つ目はzoomのミーティングの準備です。今回のzoomのミーティングはウェビナーではなく、ミーティングを選びました。ウェビナーだと観覧者の皆さまがギャラリービューに出ないからです。
ミーティングは「新しいミーティングをスケジュールする」で日時指定で予約すれば大丈夫です。
二つ目は、YouTube Liveの配信の許可です。その方法をご理解いただくには、キンスキ松井さんのブログをご覧いただくのが早いです。
なお、YouTubeアカウントに対してLive配信をを行う場合、24時間ほどの準備が必要だそうです。その許可を行うため、アカウントへLive配信の許可を申請する作業は前もって行っておきましょう。
その後の配信枠の予約などの処理は、キンスキ松井さんのブログに書かれているので、そちらをご覧ください。
三つめは、イベントの告知と招待です。それはリアルのイベントでも同じです。なので、qloba、connpass、peatix、doorkeeperなどとSNSを組み合わせて利用されるとよいでしょう。

続いて、本番中に行う作業です。これがオンライン・イベントでは大変なのです。私は今回、その事に気づきました。
まず、zoom内で司会と進行を行わねばなりません。恰好よく言うとファシリテーションです。
ただ、それはまだいいのです。たとえ今回、私が登壇のセッションを二つ受け持っていたとしても。

本当に大変なのは、皆さんがオンライン上でみるzoomの画面を制御する作業です。それは大きく分けるとギャラリービューにするか、スピーカービューにするか、という二つです。
zoomは喋っている人を自動的に判別し、その人にフォーカスを当ててくれます。
ギャラリービューは喋っている人にフォーカスを当てても、パネルの大きさは他の人と一緒です。例えば16名が参加している場合も4×4のままです。
ところがスピーカービューは話している人の枠が大きくとられ、その他の人は上部に4枠ほど出るだけです。

ここを適切に切り替えておかないと、話している人の表情が見えにくいのです。とくに、話者が画面共有をすると、その間は切り替えることができないので要注意。YouTube Liveは、ホストが設定するビューによって見え方が大きく変わってしまいます。
それにはzoomのスポットライトビデオの機能を使い、話者をロックオンしておく必要があります。この機能はキンスキの松井さんのブログをご参考になさってください。

また、こうしたイベント中の細かい制御は、zoomのホストか共同ホストでないとできないため、事前に複数の運営スタッフ間で権限を委譲しておくことをお勧めします。
ちなみに、zoomからYouTube Liveの配信制御は共同ホストではできません。ホストしかできないので注意しましょう。
もう一つ、本番中にやるべきこと。それは、画面と音声の制御です。参加者の誰かがミュートし忘れ、そのせいでセッション中に大きな物音が響いた場合、スピーカーの話が邪魔されてしまいます。その時にもホストか共同ホストが該当の方のミュートをOnにすることでつつがなく進められます。これも複数人でやり方を共有しておくべきですね。
なお、zoomでは、参加者によるミュート制御の可/不可を設定できます。ただ、不可にしてしまうと話者以外の方が喋りたい時に喋れなません。そのため、可にしておいた方がよさそうです。

やるべきことはまだあります。イベント中のTweetの盛り上げです。リアルのイベントでは、実況も含めたTweetが欠かせません。だからハッシュタグを冒頭に掲示するのです。
ですが、zoomにはミーティング内チャットという機能があります。
参加者にどちらで呟いてもらうか。これは難しいところです。
ミーティング内チャットの場合、同じユーザーインターフェースにチャットボタンがあり、そこから呟けます。そしてその内容はミーティングの参加者に限定されます。だから参加者の皆さんにとってはつぶやくためのハードルは低いです。
ただ、主催者の立場に立ってみると、ミーティング内チャットは、zoomの内に閉じてしまいます。
イベントの発信力を考えるとTwitterのほうが外へのアピールとなりやすく、Twitterで発信して欲しいという気になります。
実際、今回のkintone Café 神奈川 Vol.6ではzoomのミーティング内チャットは451件も呟かれ、43人の最大同時参加者があったのに、38人の方に呟いてもらえました。一方のTwitterは250件と、約1.8倍の差が出ました。
もちろん、zoomでもミーティング内チャットを無効にすることができます。ただ、それを禁止すると、肝心のイベントの盛り上がりに逆効果となりかねません。本末転倒もいいところです。
zoom APIを使い、ミーティング内チャットをTwitterに流せないか、調べてみましたが、どうも難しいように思えます。そもそもzoom内のチャットを外に公開してもよいか、事前に参加者の許可を取っておくべきだともいえます。
ここは、事前にTwitterやミーティング内チャットの盛り上げ役を何人かにお願いしておいたほうが良いと思います。また、ミーティング内チャットは手動で外部に保存できますので、それを定期的にTweetしてもらうのも一つの方法でしょう。
あと、今回はYouTube Liveも同時で配信しました。ですが、YouTube Liveのは双方向の関係が薄くなるため、開始直前まで配信URLを告知しませんでした。これも、本番が始まると忘れてしまいますので、拡散して下さる方を事前に決めておいた方がよいです。今回はキンスキの松井さんにフォローしてもらえました。

主催者がやるべきことはまだあります。それは、間が空いた時の埋め方です。
kintone Café 神奈川 Vol.6でも30分ほど最後にフリートークの時間ができました。
この時に質疑を行うよう促したのですが、まだまだオンライン・イベントには皆さん慣れていないのか、そこで質問を投げて下さる方は少ないようです。
他のオンライン・イベントにも何度か参加したのですが、リアルのイベントに比べると、主催者からの懇願の視線にさらされないためか、積極的に発言する方がなかなか現れないようです。
今回は冒頭でzoomの挙手機能や、はい/いいえでアンケートを取る機能を試してみたのですが、運営側スタッフでその集計や挙手へ反応する運営を打ち合わせ損ねていました。ここはぜひ、事前にコンセンサスを取っておくべきです。

結局、今回は5/9のイベントやkintone Caféの開催場所などのネタで間は持たせました。
決して皆様、話さないわけでは、ないのです。お酒が入った懇親会では、比較的に話が盛り上がりました。そのため、先にアルコールを許可しておくのも方法の一つかもしれません。
事前に質問を受け付けておき、それをネタにするというのも一つでしょう。
この部分は、これからノウハウを貯めてゆくしかないと思われます。
また、何人かに質問を投げて下さるように頼んでおくのも一つの手です。いわゆるサクラのように。

イベントの終了後にやるべきこともあります。
まず、YouTube Liveの配信停止です。今回もキンスキの松井さんにフォローしてもらわなければ、懇親会までLive配信を続けてしまったかもしれません。
そしてもう一つは、開催レポートの執筆です。
YouTubeを見直せば、内容については文章に起こすことはさほど難しくありません。
ですが、その記事に出す写真。これは案外見落としがちになると思います。
イベントの全体を表す画像をどう撮るのか。私は、ギャラリービューをスクリーンショットを撮る以外の写真が思いつきませんでした。

そんな形で、イベントの開催中は、かなり集中する仕事が多いのです。
さらにいうと、ディスプレイという狭い視野の中で作業が複数発生するため、マルチタスクに慣れていないと厳しいです。代表の私の場合、ノートパソコン一台と、Facebook messengerやイベントに関係ないお客様とのやり取りをiPad miniでこなしていました。おそらくイベント用には複数のディスプレイを併用して使いこなすしかないのでしょう。
オンラインのイベント主催とは、このようになれないうちは難しいかもしれません。

ですが、新型コロナ・ウィルスの猛威が衰えを知らない以上、オンラインのイベントはこれからも催されるはずです。ファシリテーションのスキルも求められるに違いありません。そのためには、運営上の工夫の余地はまだあるはず。
弊社も私も、引き続きノウハウの獲得に努力していきたいと思います。

最後に今回、zoomで参加して下さった皆様、YouTubeで参加して下さった皆様、事前と最中と事後にご協力くださった皆さま、本当にありがとうございました。
次回は5/9にkintone Café Online Vol.1が開催されます。
告知サイト
そこでまたお会いできればと思います。

代表の私はfreee株式会社とサイボウズ株式会社の共催イベント
freee & kintone BizTech hackでオンラインのハンズオン講師というさらに難易度の高いイベントに挑戦します。(※すでに満席なのです)
そうした挑戦を通して得たノウハウは、皆様にも還元していきたいと考えております。
今後ともよろしくお願いいたします。


ワーキングツリーにはkintoneとboxをお飾り!


kintone Advent Calendar 2019の24日目の記事です。

  Topへ↓

ん?この著者、この間もAdvent Calendarでみたで? はい。二度目の登場です。

kintoneは優れたツールですが完全ではない?
なーんてディスられても動じず、欠点を正直に認めるのもkintoneの憎めないところ。そう思ってやまない著者です。

kintoneの欠点のいくつかはすぐに挙げられます。
例えばブラウザーベースで動いているので、ファイルアップロードの作業が面倒、とか。
添付ファイルフィールドに画像データを放り込みまくると、一ユーザーあたり5GBの容量の制限が足かせになってくる、とか。

そんな限界を解消するためのささやかなクリスマスプレゼントを皆様にお届けしたいと思います。
なに、ちょっとした贈り物です。クリスマスツリーによくぶら下がっている箱のような。
箱・・・つまりboxです。
今回の記事では私の2019年の失敗事例も公開しているので、ひょっとしたら皆さまのご参考になるかもしれません。

box for kintoneのご紹介

  Topへ↑

世の中にオンラインストレージ製品はたくさんありますよね。その中でもboxが存在感を出しているのはご存じでしょうか。

kintoneとboxの連携はbox for kintoneというプラグインとして公開されています。
それを使えばブラウザー上でアップロードなどせず、ドラッグ&ドロップでブラウザー上から操作できちゃうのです。

クライアントツールのbox Driveをインストールすれば、Windowsのエクスプローラと同じ操作でbox上にファイルをアップロードできてしまう。なんて優れもの。

boxを使うと無尽蔵(契約プランによる)を誇る容量にファイルを置きまくり。
それをkintoneの画面上からに自由に呼び出せる。素晴らしい!
上に書いたkintoneの弱点を周囲のツールが補ってくれるいい例です。
本稿もそうしたkintoneを補ってくれる一つの例としてお役に立てれば幸いです。お日柄もよいので。

box for kintoneの使い方は、Cybozu developer networkにも出ています。
https://developer.cybozu.io/hc/ja/articles/205070124-Box-for-kintone その記事に従えば、簡単にbox for kintoneを導入できるはずです。

ところが、この記事で書かれているのは、一つのレコードに一つのフォルダーを対応させるところまで。
kintoneでちょっとしたシステムを作ろうとすれば複数アプリにまたがった構成が必要です。それに応じてboxのフォルダー構成も複数の階層にまたがってしまいます。

合同会社アクアビットダム設計なる会社

  Topへ↑

たとえば、大阪と東京に支店がある合同会社アクアビットダム設計があったとします。
この会社はダムを独自の技術で製造し、お客様にお納めしている設定です。

受注システムをkintoneで構築するにあたり、
大阪支店[組織]の
長井何某[個人]が担当する
担当案件[案件]の
施行状況[施工]と
湛水状況[湛水]を管理すると仮にしましょう(工程はしょりすぎ。ちなみに最後の工程は水を貯める工程です)。

他に顧客マスタがあるでしょうがここは割愛。また、[組織]と[個人]はアプリではなく、kintoneのアカウントを使用する想定です。
この場合3アプリですね。

ここでご注文からの流れをkintoneで管理したとしましょう。
各アプリの連動はkintoneのアクション機能を使ったとします。

ダム完成までにはさまざまな状況を報告していかねばなりません。するとダムの進捗に合わせて写真が大量に溜まっていきます。
kintoneの添付ファイルフィールドにファイルをアップしていると、すぐに容量が危うくなりかねません。
ここでboxの出番です。

ここでboxで写真を管理しようとした場合、box内のフォルダー構成はこのようになると思います。

さて、先ほどご紹介したbox for kintoneを思い出してみましょう。
プラグイン設定画面にルートフォルダーのIDを設定していましたね。

つまり、アプリ自体にルートフォルダーのみを作る仕様。
それって、どのレコードであろうと共通で1つのフォルダーだけ、、、
いやいや多層boxと多層アプリでは対応できないのはちょっと、、、
結論! box for kintoneだとちょっとキツイかも。

合同会社アクアビットダム設計にboxを

  Topへ↑

ということで、本稿では多段階にわたるboxの連動例をお伝えしたいと思います。また、その時にしでかしてしまった失敗と、そのリカバリ例もお伝えしたいと思います。

まず、話を簡単にするため、合同会社アクアビットダム設計としてのルートフォルダーを設定しておきましょう。

さらに、支店ごとにフォルダーを設定し、支店の配下に担当ごとのフォルダーも生成しておくと話が早いですね。

実際のboxのフォルダー構成はこんな感じ。

ここでルートフォルダーのフォルダーIDを取得しておきます。boxの画面から取れます。

その状態で、案件アプリに新規レコードを登録します。
案件アプリの項目には案件の主管支店と、案件の主担当を指定するフィールドも忘れずに。もちろん必須項目として。

なぜ必須項目にするのでしょう。
その理由は、レコードが保存成功後、案件フォルダーを作る際にどこのフォルダーの配下に作成するか決めなければならないためです。

boxのフォルダー生成APIについて

  Topへ↑

ここでboxの仕様を押さえておきましょうか。
サービスの仕様を確認するには、APIから逆引きしたほうが理解しやすい。いわゆる技術者あるあるです。
boxのAPIはこちらのサイトをご覧になると良いでしょう。
https://ja.developer.box.com ・・developerサイトトップ
https://ja.developer.box.com/reference ・・APIレファレンス

boxのフォルダー作成の項を読むと、親であるフォルダーのIDがパラメーターとして必須のようです。
編集時には親フォルダーのIDは必須ではなくなりますが、もしフォルダーの場所を移動する際は親フォルダーのIDは指定しなければなりません。

案件フォルダーを作る際は、親となる担当者フォルダーのIDを把握しておかねばなりません。
そしてその上の支店フォルダーも。

つまり、案件レコードの保存のタイミングで行うべきことは、まず、そのレコードの支店フィールドの値に等しい支店フォルダーを検索することです。
その際、基準となるのはルートフォルダーです。

ルートフォルダーの下にある支店フォルダーを検索し、そのIDを特定します。
間髪入れずに支店フォルダーの配下にある担当者フォルダーを検索します。
これは同一担当者が複数支店にフォルダーを持っている場合など、運用も考慮していますが、支店フォルダーのIDを内部で保持できるのであれば、いきなり担当者フォルダーから検索してもよいです。
重要なのは案件フォルダーを作成するにはその親フォルダーのIDを事前に必ず保持しておくことです。

先ほど、kintoneの案件アプリの支店と担当者の両フィールドは必須でなければならないとしたのには、そういう理由があったのです。
これら二つのフィールドの値がないと、案件を保存する際に生成されるべき案件フォルダーの保存先が迷子になってしまうので。

JavaScriptで実装してみた

  Topへ↑

続いてはいよいよboxの操作を行います。

その前に本稿ではboxの権限周りには踏み込まないことを言っておきます。
OAuthについては、もともとbox  for  kintoneで用意されていたclient IDを使用します。本当はbox内でアプリを作成し、そのアプリ内で設定した権限を認証しなければならないのですが。
box for kintoneに甘えてしまいましょう。

ついでにpromise処理が考慮されたAPI実行部分もbox for kintoneの処理を流用させていただきましょう。

処理の大まかな順序としては以下の通りです。
ただ、のちに述べますが、このコードは動きません。なのでコードは画像として参考程度に載せます。

まず、イベントが動くタイミングはapp.record.create.submit.successです。新規作成処理成功後ですね。

処理の都合上、この中で別のアプリに更新を行い、その結果が成功した場合にboxフォルダー生成処理を呼び出しています。

boxフォルダー生成処理では、まずルートフォルダーから支店フォルダーを検索します。
続いて支店フォルダーから担当者フォルダーを検索します。

ここでboxの検索の仕様が立ちふさがってきます。
boxの検索仕様として、対象の種類、生成時刻、オーナーIDや親フォルダーIDなどは指定できるのですが、肝心の文字列を完全一致で検索できないのです。queryというパラメータがあるにもかかわらず、そこに指定した文字列は曖昧検索として処理されてしまうのです。
APIレファレンス

つまり、親フォルダーに属する検索対象が複数ありうる場合、検索文字列に工夫が必要です。例えば姓名の間にスペースが入る場合など。
「長井 権兵衛」と「長井 主水」が対象のフォルダー配下にあって「長井 権兵衛」を検索したい場合、queryに「長井 権兵衛」を設定してもマッチしません。
ではどうやればよいか。
スペースの前後の文字列で検索するのです。
この場合、「長井」または「権兵衛」で検索します。すると前者は二件がヒットし、後者は一件がヒットします。
その結果を再度ループして回し、一件ごとにname属性の値が検索文字列に一致するかを確認する。
そのような面倒な処理がboxの検索には必要です。

このコードも実際は使っていませんが、軽く提示します。

これで、担当者フォルダーIDまで求められました。

boxのフォルダー生成と検索にまつわる問題

  Topへ↑

続いてはフォルダーの生成に移りましょう!
APIレファレンス

生成にあたっては、名前と親フォルダーのIDを指定するだけです。

これで、案件レコードが保存されたら案件フォルダーを作成するところまでができました。

ここで当初想定していたboxの構成を見てみましょう。
案件フォルダーの配下に「提案状況」「施工状況」「湛水状況」の三フォルダーがあります。
このフォルダーの生成にも実は厄介な問題が潜んでいます。

例えば、案件のレコードが保存されました。そして案件フォルダーが生成されました。
そしたら、アクション機能によって施工状況アプリにレコードをコピーし、施工状況アプリでもレコードが保存された瞬間、案件レコードの時と同じように施工状況フォルダーを生成すればええんちゃうの?と思ったでしょう。

ところが、フォルダーを生成するには親のフォルダーの指定が必須です。
親フォルダー、つまり案件フォルダーをフォルダー生成処理の直前で検索してフォルダーIDを取得しなければなりません。
ところがこの親フォルダーの検索取得にはひとつハードルが控えています。そのハードルとは、コンテンツが生成されてから検索可能となるまでに時間がかかる、というものです。
boxはなんらかのコンテンツが作成されてから、それが検索可能となるまでにbox内部でindexを構築しており、それに時間が掛かるのです。
boxの APIレファレンスには以下のように書かれています。

つまり、案件フォルダーの生成からすぐ、施工状況アプリのレコードを保存した場合、親となるべきフォルダーが検索できないため、親フォルダーの指定ができないのです。

boxを多層構造でkintoneと連動させる場合、この仕様上の制限は現状では避けられません。

この制限を回避するため、発想を切り替えました。
つまり、案件フォルダーが生成された後、同時に配下のフォルダーも作ってしまうのです。

box APIでは、フォルダー生成が成功した時点で返り値として生成されたフォルダーのIDが得られます。このIDを使えば配下のフォルダーも即時に生成できます。

このコードも実際は使っていませんが、軽く提示します。

この下の処理ではさらに生成した案件フォルダーのURLを取得し、そのURLやフォルダーIDをkintoneの2アプリに更新して設定しています。

ここまででkintoneのapp.record.create.submit.successイベントを見てきました。その結果、実装ができそうです。
テストでもフォルダーが意図通りに生成されました。開発用のPCでも、お客様のご担当者様のPCでも。

バグ大魔王降臨!

  Topへ↑

ところが! やったと思った安心のかげに潜むのが落とし穴。バグが出てしまったのです。
テストではうまく動いていたのに、いざ本番になるとうまくいかない。なんということでしょう!

実は、その根本的な原因は今もなお究明できていません。
事象としてはboxにAPIリクエストを投げた後、何も戻ってこないのにプログラムが終了してしまいます。httpレスポンスすら帰ってこずに。
それも終了する場所がまちまちなのが始末が悪い。複雑なPromiseの構造に加え、referredを混在させたことにも問題があったのかもしれません。
この不具合がやっかいなのは、boxからのレスポンスを待つ間、app.record.create.submit.successの結果が完了できないことにあります。その間、ブラウザーは固まってしまい、kintoneを利用されている皆様にはただ困惑が。

そして、この不具合の原因がブラウザーにあるのか、box側にあるのか、kintone内部にあるのか、それともPCのスペックにあるのか。はたまたネットワーク環境によるものなのか。いまだに分っていません。
ただ、ブラウザー上でレスポンスを待つ運用はまずい、という悔いだけは骨身に沁みました。
私はその原因を追究するよりもお客様の運用を円滑に進めることを優先しました。
その決断として、ブラウザーに依存する実装を止めました。

AWSへ処理を移管

  Topへ↑

では、どうすればよいか。

幸いなことにkintoneにはWebhookという機能が備わっています。Webhookには、レコード保存時にWebhookのリクエストを受け付けてくれるWebhook URLを設定できます。

私がWebhook URLとして設定したのはAWSのAPI Gatewayで設定したURLでした。
API Gatewayについての説明は割愛しますが、kintoneから受け取ったWebhookのリクエストに含まれるJSONを読み取り、それを後続の処理に渡すことができます。
後続の処理にはAWS Lambdaを選びましたので、同じAWS上で処理が連携できます。

AWS LambdaではNode.jsを使い、ほぼkintoneのkintone.app.create.submit.successで実装したのに近いコーディングを行いました。
box Node SDKがAWS Lambdaから簡単に使用できるので、それを使えば似たような実装ができるのです。
ただし、boxのアプリは一から作る必要があります。設定もあれこれ行う必要が生じました。
最初、こちらのブログの力も借りました。ありがとうございました。
Lambda関数のコードを以下に掲示します。なお、このコードは動いているものを基にいろいろといじっているので参考になると思います。

/**
 * This sample demonstrates how to call Box APIs from a Lambda function using the Box Node SDK.
 *
 * For step-by-step instructions on how to create and authorize a Box application,
 * see https://github.com/box/samples/tree/master/box-node-lambda-sample.
 */
const BoxSDK = require('box-node-sdk');                                                // Node.jsのbox-node-sdkモジュールを呼び出す
const request = require('request');                                                    // Node.jsのrequestモジュールを呼び出す
const boxConfig = JSON.parse(process.env.BOX_CONFIG);                                  // AWS Lambdaの環境変数のBOX_CONFIGの値をJSONで扱えるように

boxConfig.boxAppSettings.appAuth.keyID = boxConfig.boxAppSettings.appAuth.publicKeyID; // 9行目で取り出したkeyIDにpublicKeyIDを代入

const sdk = new BoxSDK(boxConfig.boxAppSettings);                                      // 9行目で取り出したboxAppSettingsをsdkに代入

/**
 * Create a service account client that performs actions in the context of the specified
 * enterprise.  The app has a unique service account in each enterprise that authorizes the app.
 * The service account contains any app-specific content for that enterprise.
 * Depending on the scopes selected, it can also create and manage app users or managed users
 * in that enterprise.
 *
 * The client will automatically create and refresh the service account access token, as needed.
 */
const client = sdk.getAppAuthClient('enterprise', boxConfig.enterpriseID);             // boxアプリが適用できるアカウントのグローバル設定を管理

var DOMAIN = '*********.cybozu.com'; //kintone環境のドメイン                            // *****はご使用のkintoneのサブドメインを
var APP_ID_1287 = 1287;   //案件管理アプリのアプリID
var BASE_URL = "https://" + DOMAIN + '/k/v1/';
var APITOKEN_1287 =  "kintoonkaramottekitatookunwokokoniiretene";                      // kintoneの案件アプリのAPIトークン
var headers_1287 = {'X-Cybozu-API-Token': APITOKEN_1287};                              // リクエストで使用するヘッダ
var FolderId;
var updaterecordid_1287;

exports.handler = (event, context, callback) => {                                      // eventはkintoneのWebhookからAPI Gatewayを経由したレコード情報
                                                                                       // contextはLambda関数に関する情報
    const API_BASE_PATH = 'https://api.box.com/2.0';                                   // box Node SDKの文法に準拠

    // targetnameはコンテンツの文字列
    // typeはコンテンツの対象。本稿の場合はfolder
    // content_typesは検索対象とするプロパティ。本稿の場合はname
    // limitは検索結果として戻す件数。
    // idsは親フォルダーのフォルダーID
    // methodは本稿では全てGETなので使用していない
    // dataは本稿の場合検索対象(支店,担当者,案件No)のうち、担当者の場合["担当名"]で渡ってくる。
    function searchFolder(targetname, type, content_types, limit, ids, method, data, success, error) {  //157,159,161行目から呼び出されて検索処理を実施
        if (data !== undefined) {                                 // dataが指定されている場合
            if (data[0] === "担当名") {                            // dataの配列の最初の要素が"担当名"の場合
                targetname = targetname.split(' ')[0];           // 受け取るtargetnameは「長井 権兵衛」の様に全角スペースで区切られた姓名なので姓を取得
            }
        }

        return new Promise(function (resolve, reject) {           // Promiseを設定
            client.search.query(                                  // 24行目でclientとして承認されたbox Node SDKのsearchクラスのquery関数を呼び出し
                "\"" + targetname + "\"",                         // 最初のパラメーターは検索対象文字列。文字列なのでエスケープした""で囲む。でも曖昧検索
                {                                                 
                    fields: 'id,name,modified_at,extension,permissions,collections',  //検索結果として返すコンテンツのプロパティ
                    type: type,                                                       //folder
                    content_types: content_types,                                     //検索対象はnameプロパティ
                    limit: limit,                                                     //結果として返す件数
                    ancestor_folder_ids: ids,                                         //親フォルダーID 
                    offset: 0                                                         //オフセットしないので0
                })
                .then(function(results){                                              //結果が取得されたのでこのPromiseチェーンへ
                    if (data !== undefined) {                                         //dataが指定されている場合
                        if (data[0] === "担当名") {                                   //dataの配列の最初の要素が"担当名"の場合
                            for (var i = 0; i < results.entries.length; i++) {        //戻り値の件数分(limitで指定した件数分)
                                if (results.entries[i].name === data[1]) {            //戻り値のnameプロパティがdataの2番目の要素(担当名)か
                                    resolve(results.entries[i].id);                   //Promiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                                }
                            }
                        }
                    } else {
                        resolve(results.entries[0].id);                               //Promiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                    }
                })
                .catch(function(error){ // エラーの場合
                    reject(error);
                });
        });
    }

    // createParamは生成フォルダー名と親フォルダーIDが含まれたJSONオブジェクト
    function postFolder(createParam) {                     //176行目から呼び出されてフォルダー生成処理を実施
        return new Promise(function (resolve, reject) {    // Promiseを設定
            client.folders.create(createParam.parent.id, createParam.name)  //24行目でclientとして承認されたbox Node SDKのfolderクラスのcreate関数を呼出
                                                                            //1つ目は親フォルダーID、2つ目は生成するフォルダーの名称 
                .then(function(results){                                    //85行目の処理が成功したのでこのPromiseチェーンへ
                    var ankenid = results.id;                               //生成したフォルダーIDを以下のforeach内で使うためにankenidに代入 
                    var subfolders = [                                      //生成した案件フォルダーの配下に作成する三つのフォルダー名を配列にしています
                        "提案資料",
                        "施工状況",
                        "湛水状況"
                    ];

                    var promiseset = [];                                    //三つのフォルダーの生成が終わるまで待つPromiseを三つ作るので配列を設定
                    subfolders.forEach(function(val,index,ar){              //89行目で生成した配列の各要素をループします
                        promiseset[index] = new Promise( function( resolve, reject ) {  //95行目で生成した配列にPromiseを設定します。
                            client.folders.create(ankenid, val)             //24行目でclientとして承認されたbox Node SDKのfolderクラスのcreate関数を呼出 
                                                                            //1つ目は親フォルダーID(案件フォルダー)、2つ目は生成するサブフォルダーの名称
                                .then(function(results){                    //98行目の処理が成功したのでこのPromiseチェーンへ
                                    resolve(results.id);                    //97行目のPromiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                                }).catch(function(error){                   //98行目の処理が失敗したのでこのPromiseチェーンへ
                                    reject(error);                          //97行目のPromiseは失敗したとエラーオブジェクトを返す
                                });
                        });
                    });
                    Promise.all( promiseset )                               //97行目で設定した三つのPromiseが全て完了したらここに来る
                        .then( function ( message ) {
                        resolve(ankenid);                                   //84行目のPromiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                    })
                        .catch( function ( reason ) {                       //97行目で設定した三つのPromiseのどれかが失敗したらここに来る
                            console.log( reason ) ; // "失敗!!"
                        reject(false);                                      //84行目のPromiseは失敗したとエラーオブジェクトを返す
                    });
                })
                .catch(function(error){                                     //85行目の処理は失敗したらここに来る
                    reject(error);                                          //84行目のPromiseは失敗したとエラーオブジェクトを返す
                });
        });
    }

    // boxフォルダーIDは更新対象となるフォルダーID
    // createParamは更新フォルダー名と親フォルダーIDが含まれたJSONオブジェクト
    function putFolder(boxフォルダーID, updateParam) {             //165行目から呼び出されてフォルダー更新処理を実施
        return new Promise(function (resolve, reject) {         // Promiseを設定
            client.folders.update(boxフォルダーID, updateParam)    //24行目でclientとして承認されたbox Node SDKのfolderクラスのupdate関数を呼出
                                                                //1つ目は対象となるフォルダーID、2つ目は更新するフォルダー情報の含まれたJSONオブジェクト 
                .then(function(results){                        //126行目の処理が成功したのでこのPromiseチェーンへ
                    resolve(results.id);                        //125行目のPromiseは完了したと戻り値のidプロパティ(フォルダーID)を返す
                })
                .catch(function(error){                         //126行目の処理が失敗したのでこのPromiseチェーンへ
                    reject(error);                              //125行目のPromiseは失敗したとエラーオブジェクトを返す
                });
        });
    }

    function getFolderURL(createdid) {                          //178行目から呼び出されてフォルダーの共有処理を実施
        return new Promise(function (resolve, reject) {         // Promiseを設定
            client.folders.update(createdid, {shared_link: client.accessLevels.OPEN}) 
                                                                //24行目でclientとして承認されたbox Node SDKのfolderクラスのupdate関数を呼出
                                                                //1つ目は対象となるフォルダーID、2つ目は更新するフォルダーのプロパティ(共有設定)
                .then(function(results){                        //139行目の処理が成功したのでこのPromiseチェーンへ
                    resolve(results.shared_link.url);           //138行目のPromiseは完了したと戻り値の共有URLプロパティ(リンクURL)を返す
                })
                .catch(function(error){                         //139行目の処理が失敗したのでこのPromiseチェーンへ
                    reject(error);                              //138行目のPromiseは失敗したとエラーオブジェクトを返す
                });
        });
    }
    function createBoxFolder(支店, 担当者, 案件No, boxフォルダーID) {  //212行目から呼び出されてフォルダーの共有処理を実施
        return new Promise(function (resolve,reject) {             // Promiseを設定
            var rootfolder = "12345678910";                        //boxのフォルダー制御のルートとなるフォルダーのフォルダーIDを静的に代入
            var ownerbranchfolder;                                 //支店フォルダーのフォルダーID
            var personinchargefolder;                              //担当者フォルダーのフォルダーID
            var createParam;                                       //searchFolder関数へはダミーオブジェクト。putfolderとpostfolderへはJSONオブジェクト

            searchFolder(支店, "folder", "name", 10, rootfolder, 'GET', createParam).then(function (branchfolderid) {  //45行目へ
                ownerbranchfolder = branchfolderid;                           //searchFolderからの返り値を上位スコープのownerbranchfolderへ代入
                searchFolder(担当者, "folder", "name", 10, ownerbranchfolder, 'GET', ["担当名",担当者]).then(function (personfolderid) {  //45行目へ
                    personinchargefolder = personfolderid;                    //searchFolderからの返り値を上位スコープのpersoninchargefolderへ代入
                    searchFolder(案件No, "folder", "name", 10, personinchargefolder, 'GET', createParam).then(function (projectfolderid) {  //45行目へ
                        var name = "案件No" + " " + 案件No;                    //生成/更新する案件フォルダーの名称を設定する
                        createParam = {name: name, parent: {id: personinchargefolder}};  //案件フォルダーの設定情報をJSONオブジェクトに組み立てる
                        if (projectfolderid.length > 0) {                     //161行目で案件フォルダーが存在した場合(フォルダー情報更新)
                            putFolder(projectfolderid, createParam).then(function (updatedid) {  //124行目へ
                                FolderId = updatedid;                         //161行目の処理で得た更新したフォルダーIDをスコープ外の168行で使うため
                                getFolderURL(updatedid).then(function (updatedurl) {             //137行目へ
                                    resolve(FolderId+"****"+updatedurl);      //151行目のPromise完了をフォルダーIDと共有URLプロパティ(リンクURL)で返す
                                }).catch(function(error){                     //167行目の処理が失敗したのでこのPromiseチェーンへ
                                    reject(error);                            //151行目のPromiseは失敗したとエラーオブジェクトを返す
                                });
                            }).catch(function(error){                         //165行目の処理が失敗したのでこのPromiseチェーンへ
                                reject(error);                                //151行目のPromiseは失敗したとエラーオブジェクトを返す
                            });
                        } else {
                            postFolder(createParam).then(function (createdid) {       //83行目へ
                                FolderId = createdid;                                 //生成したフォルダーIDを以下の179行目で使うためにFolderIdに代入
                                getFolderURL(createdid).then(function (createdurl) {  //137行目へ
                                    resolve(FolderId+"****"+createdurl);      //151行目のPromise完了をフォルダーIDと共有URLプロパティ(リンクURL)で返す
                                }).catch(function(error){                             //178行目の処理が失敗したのでこのPromiseチェーンへ
                                    reject(error);                                    //151行目のPromiseは失敗したとエラーオブジェクトを返す
                                });
                            }, function(res) {                                        //176行目のフォルダー生成処理でrejectレスポンスが返った場合
                                if (res.status && res.status === 409) {               //176行目のフォルダー生成処理でrejectレスポンスが409返った場合
                                    if (res.context_info                              //176行目のフォルダー生成処理でrejectレスポンスが競合を示した場合
                                        && res.context_info.conflicts
                                        && res.context_info.conflicts.length > 0) {
                                        return;                                       //150行目のcreateBoxFolder関数を終える
                                    }
                                }
                            }).catch(function(error){                                 //176行目のフォルダー生成処理でエラーが帰った場合
                                reject(error);                                        //151行目のPromiseは失敗したとエラーオブジェクトを返す
                            });
                        }
                    }).catch(function (error) {                                       //161行目のフォルダー検索処理でエラーが帰った場合
                        // 非同期処理失敗。呼ばれない
                        console.log(error);
                    });
                }).catch(function (error) {                                           //159行目のフォルダー検索処理でエラーが帰った場合
                    // 非同期処理失敗。呼ばれない
                    console.log(error);
                });
            }).catch(function (error) {                                               //157行目のフォルダー検索処理でエラーが帰った場合
                // 非同期処理失敗。呼ばれない
                console.log(error);
            });
        });
    }

    var recordjson = JSON.parse(event.body);                                 //34行目で受け取ったkintoneのWebhookのレコード情報をJSON形式で扱えるように
    updaterecordid_1287 = recordjson.record.レコード番号.value;               //210行目のレコードデータの「レコード番号」フィールドの値を代入
    createBoxFolder(recordjson.record.支店.value[0].code,                    //150行目へ
                    recordjson.record.担当者.value, 
                    recordjson.record.案件No.value, 
                    recordjson.record.boxフォルダーID.value).then(function(idurl) {
        if (idurl) {                               //212行目のcreateBoxFolderの戻り値(168、179行目で値設定)
            var targetrecordids = [updaterecordid_1287+"**"+APP_ID_1287];   //211行目で設定したレコード番号と27行目で設定したアプリID
            var kintonepromiseset = [];                                     //kintoneのレコードアップデートが終わるまで待つPromiseの配列を設定
            targetrecordids.forEach(function(val,index,ar){                 //217行目で生成した配列の各要素(本稿では1つ)をループします
                kintonepromiseset[index] = new Promise( function( resolve, reject ) {  //218行目で生成した配列にPromiseを設定します。
                    var body_post = {                                                  //kintoneの既存案件アプリを更新するレコードを組み立てます。
                        app: val.split("**")[1],                                       //217行目で設定した配列の**で区切られた右側(アプリID)
                        id: val.split("**")[0],                                        //217行目で設定した配列の**で区切られた左側(レコード番号)
                        record: {
                            boxフォルダーID: {
                                value: idurl.split("****")[0]                       //212行目のcreateBoxFolderの戻り値の****で区切られた左のフォルダーID
                            },
                            表示: {
                                value: idurl.split("****")[1].replace("*******.box.com","app.box.com")
                                         //212行目のcreateBoxFolderの戻り値の****で区切られた右のURL(契約のboxのサブドメインをapp.box.comに置換の必要あり)
                            }
                        }
                    };
                    var options_getsalesamount = {                            //リクエストのbody部分を組み立てます。
                        url: BASE_URL + 'record.json',                        //28行目で設定したURLのルートと一行レコードの更新なのでrecord.jsonを連結
                        method: 'PUT',                                        //更新なのでPUT
                        headers: headers_1287,                                //30行目で設定したAPIトークン
                        'Content-Type': 'application/json',                   //リクエストのボディ部分のタイプ
                        json: body_post                                       //221行目で設定したボディ部分
                    }
                    //レコードを取得
                    request(options_getsalesamount, function (error, response, body) {    //Node.jsのrequestモジュールで234行のリクエストを送信
                        if (error) {                                                      //242行目の値がerrorだったら
                            console.log('Error: ' + error.message);
                            reject();                                                     //220行目のPromiseは失敗したとエラーオブジェクトを返す
                        }
                        console.log("kintone recordput:succcess"+val);
                        resolve();                                                        //220行目のPromise完了を返す
                    });
                });
            });
            Promise.all( kintonepromiseset )                                              //220行目で設定したPromiseが全て完了したらここに来る(本稿は1つ)
                .then( function ( message ) {                                             //252行目の処理が成功したのでこのPromiseチェーンへ
                    context.done(null, {text: "kintone POST and Box Folder Create success!"});  //Lambdaの処理結果をログとして残す
            })
                .catch( function ( reason ) {
                    context.done(null, {text: "Box Folder Create failed!"});              //Lambdaの処理結果としてエラーログ
                return;
            });
        } else {
            context.done(null, {text: "Box Folder Create failed!"});                      //Lambdaの処理結果としてエラーログ
        }
    }, function(res) {                                                                    //212行目の返り値がrejectで戻ってきた場合
        context.done(null, {text: "Box Folder Create failed!"});                          //Lambdaの処理結果としてエラーログ
        return false;                                                                     //212行目の結果としてfalseを返す
    });
};

 

なんとか実装

  Topへ↑

いずれにせよ、私が2019年に出した唯一の大きなバグがこれでした。
結局、バグが出てから実運用にこぎつけるまでにさらに二カ月ほどの時間をいただきました。お客様にも多大なご迷惑をおかけしてしまいました。

これが実装できたことで、案件アプリにレコードを登録した時点で、Webhookが発動し、AWS API GatewayからAWS Lambdaを介してboxへのフォルダー生成と、レコードに対応するboxのURLとフォルダー番号をkintoneの該当レコードに登録することができました。

kintoneの画面上にboxのフォルダーを出す部分はbox for kintoneの内部にも書かれている通りです。
実際それを使わせていただいています。ありがとうございます。
以下にコードを載せていますが、疲れてきたのでコード内のコメントは割愛します。ごめんなさい。

(function() {
    'use strict';

    var BOX_CLIENT_ID = 'wkgp4k64whsha8mwvg7k5k63cim82mmv';   //sample_plugin_default
    // localStorage
    var LOCAL_STORAGE_PREFIX = 'kintone.plugin.' + BOX_CLIENT_ID;
    var LOCAL_STORAGE_JUDGED_ALLOW_ACCESS = LOCAL_STORAGE_PREFIX + '.judgedAllowAccess';

    var config = [];

    var BOX_EMBED_WIDTH = 840;
    var BOX_EMBED_HEIGHT = 420;

    var getUrl = function(path) {
        var matchedGuestSpacePath = location.pathname.match(/^\/k\/(guest\/\d+\/)/);
        var guestSpacePath = '';
        if (matchedGuestSpacePath !== null && matchedGuestSpacePath.length === 2) {
            guestSpacePath = matchedGuestSpacePath[1]; // "guest//"
        }
        var apiPath = '/k/' + guestSpacePath + path;
        return apiPath;
    };

    var boxApi = {
        clientInfo: {'provider': 'box', 'client': BOX_CLIENT_ID},

        getAccessToken: function() {
            // add a hash parameter for distinguishing OAuth redirect
            var delimiter = (location.hash.indexOf('#') === 0) ? '&' : '#';
            location.hash += delimiter + BOX_CLIENT_ID + '.oauth_redirect=true';
            kintone.oauth.redirectToAuthenticate(this.clientInfo, location.href);
        },
        hasAccessToken: function() {
            return kintone.oauth.hasAccessToken(boxApi.clientInfo);
        }
    };

    var validateConfig = function(record) {
        config['folderId'] = '0';//Box親フォルダーID
        config['keyFld'] = '顧客名';//kintoneキーフィールド
        config['boxUrl'] = '表示';//Box共有リンクの格納先
        config['boxFolderId'] = "boxフォルダーID";
        config['access'] = 'Open';//Box共有リンクのアクセス権[Collaborator/Company/Open]
        config['prohibitToDownload'] = 'false';//コラボレータにのみダウンロードを許可する

        if (!config) {return false; }
        return true;
    };

    var decorateBoxLinkField = function(boxUrl) {

        var boxLinkPattern = /^https:\/\/([a-zA-Z0-9]+).box.(com|net)(\/s\/[a-z0-9]+)$/;
        var match = boxUrl.match(boxLinkPattern);
        if (!match) {
            return;
        }
        var iframeSrc =
            'https://app.box.com/embed_widget/000000000000' +
            match[3] +
            '?theme=gray' +
            '&show_parent_path=no' +
            '&show_item_feed_actions=no' +
            '&partner_id=233';

        var elEmbed = kintone.app.record.getFieldElement(config.boxUrl);
        if (elEmbed === null) {return; }
        $(elEmbed).empty();

        var width = BOX_EMBED_WIDTH;
        var height = BOX_EMBED_HEIGHT;

        $(elEmbed).parent().css({
            'width': (width + 100) + 'px',
            'height': 'auto',
            'background-color': 'rgba( 255, 255, 255, 0 )'
        });
        var embedIframe = $('', {
            src: iframeSrc,
            width: width,
            height: height,
            frameborder: '0',
            allowfullscreen: 'true',
            allowscriptaccess: 'always'
        });
        $(elEmbed).append(embedIframe);
    };

    var judgedAllowAccessFlag = {
        isSet: function() {
            return (localStorage.getItem(LOCAL_STORAGE_JUDGED_ALLOW_ACCESS) !== null);
        },

        set: function() {
            localStorage.setItem(LOCAL_STORAGE_JUDGED_ALLOW_ACCESS, 'true');
        },

        remove: function() {
            localStorage.removeItem(LOCAL_STORAGE_JUDGED_ALLOW_ACCESS);
        }
    };

    kintone.events.on('app.record.detail.show', function(e) {
        if (validateConfig(e.record)) {
            var boxUrl = e.record[config.boxUrl].value;
            if (!e.record[config.boxUrl].value) {

                var elEmbed = kintone.app.record.getFieldElement(config.boxUrl);
                if (elEmbed === null) {return null; }
                $(elEmbed).empty();

            } else {
                decorateBoxLinkField(boxUrl);
            }
        }

        return e;
    });

    var checkAccessToken = function() {
        var oauth_redirect_param = BOX_CLIENT_ID + '.oauth_redirect=true';
        if (location.hash.indexOf(oauth_redirect_param) !== -1) {
            judgedAllowAccessFlag.set();

            // remove a hash parameter
            location.hash = location.hash.replace(oauth_redirect_param, '');

            var t = setInterval(function() {
                if (location.hash.indexOf(oauth_redirect_param) !== -1) {
                    // cancel button was clicked
                    clearInterval(t);
                    location.href = getUrl(kintone.app.getId() + '/');
                }
            }, 500);
        } else if (!judgedAllowAccessFlag.isSet() || !boxApi.hasAccessToken()) {
            kintone.oauth.clearAccessToken(boxApi.clientInfo, function(body, status, headers) {
                boxApi.getAccessToken();
                return null;
            });
        }
    };

    kintone.events.on('app.record.create.show', function(e) {
        if (validateConfig(e.record)) {
            checkAccessToken();
            e.record[config.boxUrl]['disabled'] = true;
            e.record[config.boxFolderId]['disabled'] = true;
        }

        return e;
    });

    kintone.events.on('app.record.edit.show', function(e) {
        if (validateConfig(e.record)) {
            if (!e.record[config.boxUrl].value) {
                checkAccessToken();
            } else {
//                e.record[config.keyFld]['disabled'] = true;
                e.record[config.boxFolderId]['disabled'] = true;
            }
            e.record[config.boxUrl]['disabled'] = true;
        }
        return e;
    });

    kintone.events.on('app.record.index.edit.show', function(e) {
        if (validateConfig(e.record)) {
            e.record[config.boxUrl]['disabled'] = true;
            e.record[config.keyFld]['disabled'] = true;
            e.record[config.boxFolderId]['disabled'] = true;
        }
        return e;
    });
})();

 

 

まとめ

  Topへ↑

実案件ではさらに凝った実装(フォルダー数も階層も本稿の例よりさらに多い)が施されています。
そして、古くboxが設定されていないレコードには手作業がたまに発生しているものの、実運用に乗っています。
この記事ではそれ以上の情報を出すことはお客様の業務に関わるのでここまでにしとうございます。

本稿がkintoneを運用している皆様にとって少しの手助けになれば幸せです。

kintone上で大量の添付ファイルに困っていらっしゃる方や、社内ファイルサーバーからの移行でお困りの方。他のPaaSからkintoneへ移行する作業があって、添付ファイルの扱いにお困りの方。
弊社では本稿のようなboxとkintoneの連動事例を何例も手掛けております。お困りの際はおっしゃってくださいませ。

最後に蛇足ですが、boxの案件で例に挙げた三つのダムは、私が実際に訪れてダムカードを入手した場所です。


当エントリーの参考にさせていただいたブログ

  Topへ↑

最後になりましたが、このエントリー作成にあたり、以下の2サイトからの情報を参考にさせていただきました。ありがとうございました。

 box APIレファレンス
 AWS Lambda上でBox Node SDKを利用する-九龍堂雑録


コーチングのグラフってkintoneで出せるんやって!


kintone2 Advent Calendar 2019の5日目の記事です。

  Topへ↓

突然ですが皆さん、コーチングって聞いたことがありますか?
あっ! そこのあなた、ページはそのままに!
これは間違いなくkintone Advent Calendarの記事ですから。
ほら!

きとみちゃん楽しいですよね!
https://kintone.cybozu.co.jp/jp/kitomi/

日々、お仕事に励むきとみちゃん。
きとみちゃんとお仕事をする仲間はとっても個性が豊か。

ちょっぴりあわてんぼうでドジっ子のきとみちゃんがkintoneに救われる姿は微笑ましいです。
ちなみに私は巻物で見積書を出してくださる麻呂な方が好きです。この方のお名前はなんでおじゃる?

さて、きとみちゃんがお仕事をする上で助けになる手法はkintoneの他にもさまざまなものがあります。

その中の一つが冒頭に書いたコーチングなのです。

コーチングを一言で言い表すなら、
・相手の学習や成長、変化を促し、相手の潜在能力を解放させ、最大限に力を発揮させる。
でしょうか。

詳しくはWikipediaの「コーチング」
をご覧くださいませ。

ビジネスにフォーカスを当てたコーチングの歴史はまだまだ浅いです。
ここでお伝えしておかなければならないのは、自己啓発セミナーとは違う、ということです。

と、偉そうにウンチクを述べる私ですが、コーチングを受けた経験は人生で1,2回だけ。
では、そんな私がkintone Advent Calendarで何を語るというのでしょうか。

結論を先に書いちゃうと、kintoneでこんなグラフを作ってみましょう!
ということなんですね。

グラフとデータのご説明

  Topへ↑

上に登場したのは四つの傾向を円グラフにしたものです。
それぞれの傾向の文字列にマウスを合わせると、事前に登録しておいたキーワードが出てくる。
これ、実は以前、お客様に依頼されて作ったkintoneにChart.jsを組み込んだグラフ生成の仕掛けです。

私はコーチングには無知です。
ですから、kintoneに入力画面を作り、その結果を集計することで、設問に応じた四つの傾向が算出できる、ということを知ったときは新鮮でした。

お客様によれば、
相手をほめる場合の、個人に響くキーワードは4つの傾向に分けられる
だそうです。

それに合わせて、こんな入力画面を作ってみました。

仮に20問の設問としています。それぞれの4つの傾向ごとに5問を設問しました。
それぞれの問いごとに
・よく当てはまる
・当てはまる
・当てはまらない
・まったく当てはまらない
の4種類の答えをラジオボタンで設定しています。

もちろん、さらに設問数を増やすことも可能ですし、設問数を自在に増減させたい、というご要望もあるでしょうね。
その場合はサブテーブルを使えばよさそうです。
この記事ではサブテーブルではなく、20問に固定したバージョンでお届けしてみます!

実際の内容

  Topへ↑

はい。ではアプリの設定画面です。フォームはこんな感じ。

一番左の文字列フィールドは設問の文字列を入力します。
フィールドコードは上から順にquestion_1からquestion_20としています。

真ん中のドロップダウンフィールドは4つの傾向を選びます。
フィールドコードは上から順にtrend_1からtrend_20としています。

右のラジオボタンフィールドはそれぞれの答えを入力する欄です。
フィールドコードは上から順にanswer_1からanswer_20としています。

で、続いてはグラフを表示するカスタマイズビューを設定してみましょう。

こんな感じですね。

続いてはロジックです。
実は、このグラフを作るには以下の二つのJavaScriptファイルを設定するだけ。

上に設定したのは、Chart.jsです。
Cybozu Developer Network
からCDNのページに移動してもらえれば。

そこのChart.jsに書かれているURLをコピーし、上の画面の
から

に貼って保存するだけ! きとみちゃんでもできますよね?
htttps://がダブらないようにだけ気を付けて!

続いてグラフ表示のロジックです

  Topへ↑

では続いてきとみちゃんとグラフ.jsの内容を。
ここからはVisual Studio Codeの画面にコメントを入れています。




ちょっと見にくいので、直に貼ったコードも提供します。右にスクロールしてくださいね。

(function () {
  "use strict";

  // 一覧ページ
  kintone.events.on('app.record.index.show', function(event) {                       //一覧画面表示時の定型文です
    var record = event.records[0];
    var itemcount = 20;
    var 1_Score = 0;
    var 2_Score = 0;
    var 3_Score = 0;
    var 4_Score = 0;
    var selectedScore = 0;
    var dataLabelPlugin = {                                                          //ここは以下の162行目で呼び出されるチャートのプラグインコンフィグで呼び出される部分です。
      afterDatasetsDraw: function (Chart, easing) {                                  //afterDatasetsDrawとはプラグインコアAPIとして呼び出されるChart.js内部のフックです。要は描画後です。
        var ctx = Chart.ctx;                                                         //チャートが描画されている対象のDOM要素です。157行目で定義され、159行目でChartオブジェクトに渡されます。
        Chart.data.datasets.forEach(function (dataset, i) {                          //対象チャートをループしています。データは77行目で一種類で指定していますのでループは一回のみです。               
          var meta = Chart.getDatasetMeta(i);                                        //チャートのメタデータを取得しています。データやラベルも含まれています。
          if (!meta.hidden) {                                                        //チャートのhiddenプロパティがTrueの場合そもそもチャートが描画されません。
            meta.data.forEach(function (element, index) {                            //メタ要素のデータをループします。今回は4種類ですね。
              ctx.fillStyle = 'rgb(0, 0, 0)';                                        //円グラフの中の文字の色です。rgb(0, 0, 0)は黒を表しています。
              var fontSize = 16;                                                    //36-37行目で文字の場所を設定するためのフォントサイズを16pxで設定しています。表示フォントのサイズとは別に。
              ctx.font = "24px \"Helvetica Neue\", Helvetica, Arial, sans-serif";    //これが実際に描画される文字のフォント情報です

              var sum  = function(arr) {                                             //ここでは対象となるデータの合計値を返します。4種類のデータの合計です。
                  return arr.reduce(function(prev, current, i, arr) {
                      return prev+current;
                  });
              };
              var percentString = ((dataset.data[index] / sum(dataset.data))*100).toFixed(1) + "%";  //それぞれのデータの値を全体の合計で割り、パーセントの文字列を構築します。
              var dataString = Chart.data.labels[index];                                             //それぞれのデータのラベルです。79行目で定義した4つの傾向のラベルですね。 
              ctx.textAlign = 'center';
              ctx.textBaseline = 'middle';

              var padding = 5;
              var position = element.tooltipPosition();
              ctx.fillText(dataString, position.x, position.y - (fontSize / 2) - padding);           //30行目で設定したラベルの値を計算した位置に表示します。
              ctx.fillText(percentString, position.x, position.y - (fontSize / 2) - padding + 35);   //29行目で設定した値のパーセントの文字列を計算した位置に表示します。
            });
          }
        });
      }
    };
    for (var i=1 ; i<=itemcount ; i++){                                                              //ここから76行目までは大人の事情でいろいろとあいまいですがお許しを
      switch( record['answer_' + i]['value'] ) {                                                     //要するに20レコードの設問の答えを基に四つの傾向に加算しているのです
        case 'よく当てはまる':
          selectedScore = 係数は内緒よ♪;
          break;
        case '当てはまる':
          selectedScore = 係数は内緒よ♪;
          break;
        case '当てはまらない':
          selectedScore = 係数は内緒よ♪;
          break;
        case 'まったく当てはまらない':
          selectedScore = 係数は内緒よ♪;
          break;
      }
      switch( record['trend_' + i]['value'] ) {
        case '一つ目の傾向':
          1_Score = 1_Score + selectedScore + 山藤ゆりさんに教えてもらった魔法の値を加えるの♪;           //要するに20レコードの設問の答えを基に四つの傾向に重みづけしているのです
          break;
        case '二つ目の傾向':
          2_Score = 2_Score + selectedScore + 山藤ゆりさんに教えてもらった魔法の値を加えるの♪;
          break;
        case '三つ目の傾向':
          3_Score = 3_Score + selectedScore + 山藤ゆりさんに教えてもらった魔法の値を加えるの♪;
          break;
        case '四つ目の傾向':
          4_Score = 4_Score + selectedScore + 山藤ゆりさんに教えてもらった魔法の値を加えるの♪;
          break;
      }
    }
    1_Score = ロジック関数は内緒よ♪(1_Score);
    2_Score = ロジック関数は内緒よ♪(2_Score);
    3_Score = ロジック関数は内緒よ♪(3_Score);
    4_Score = ロジック関数は内緒よ♪(4_Score);                                          //さらに四つの傾向に値を秘密ロジックで精緻化しています。この辺も大人の事情が絡んでいます。
    var pieChartData = {                                                             //161行目でChartオブジェクトに渡されるデータとラベルと背景色のホバー色や枠の組み合わせです。四要素です。   
      labels : ["リーダー合理系","アイディア活動系","ヘルプ支援系","クール分析系"],       //ラベルですね。四つの要素に分かれています。
      datasets : [                                                                   //四つの要素のそれぞれの色の指定です。
        {
          backgroundColor: [
            '#ff6384',
            '#36a2eb',
            '#cc65fe',
            '#ffce56'
          ],
          hoverBackgroundColor: [
              "#FF2384",
              "#3662EB",
              "#cc25fe",
              "#FF8E56"
          ],
          hoverBorderColor: [
              "#000000",
              "#000000",
              "#000000",
              "#000000"
          ],
          hoverBorderWidth: [
              2,
              2,
              2,
              2
          ],
          data : [1_Score,2_Score,3_Score,4_Score]                                   //四つの要素の値です。大人の事情で実際の回答から複雑に計算された結果が格納されます。
        }
      ]
    }
    var tooltipkeyword = {                                                           //ここは四つの傾向ごとに176行目で乱数を設定し、任意のキーワードを表示するようにしています。
      type : [
        {
          word : [
            '同業者もあの人を噂している',
            '他の部署でも話題になっている',
            '○○さんしかできない',
            '自分で判断し、動ける人',
            '部署のメンバーに信頼されている',
            'あのひとには任せられる'
          ],
          title : "任せる、難題、未知の分野、他に頼めない、誰にもできない"
        },
        {
          word : [
            '発想がおもしろい!!',
            '一緒にいるだけで楽しい!!',
            'さすがアイデアマン!!',
            'すばらしいサービス精神!!',
            'うちの部署のムードメーカー!!',
            'その自由な発想がうらやましい!!'
          ],
          title : "自由にして、思いっきり、楽しく、面白く、みんなでいっしょ"
        },
        {
          word : [
            'みんなが働きに感謝している',
            '縁の下の力持ち',
            '一緒にいて落ち着く',
            '丁寧で親切で信頼できる',
            '細かいところによく気が付く',
            '相手の気持ちを分かってくれる'
          ],
          title : "感謝、ありがとう、仲良く、話し合い、相手の気持ち"
        },
        {
          word : [
            '詳しく業務を理解している',
            '商品のことをよく知っている',
            'わが社のことになんでも詳しい',
            'うちの課の歩く辞書',
            'あの人に聞けば間違いない',
            'このデータ量は大したもの'
          ],
          title : "情報、正確、正しく分析、予定通り、計画通り"
        }
      ]
    }
    var canvas = document.getElementById('canvas').getContext("2d");                 //Chartが描画されるDOM要素を指定するChart.jsの定型文です。id="canvas"はカスタマイズビューで指定しました。
    canvas.canvas.height = 256;                                                      //描画される領域の高さを指定しています。
    var test_chart = new Chart(canvas, {                                             //ここでChartオブジェクトをインスタンスとして実体化させています。
      type: 'pie',                                                                   //type: 'pie'はグラフの種類ですね。円グラフです。
      data: pieChartData,                                                            //77行目で定義したデータの実態です。
      plugins: [dataLabelPlugin],                                                    //プラグインコンフィグで関数を呼び出すことができます。その関数は13行目をご参照ください。
      options: {                                                                     //ここからはオプション情報です。
        animation: {
          animateRotate: true,
          animateScale: true
        },
        tooltips: {
          titleFontSize: 48,
          bodyFontSize: 36,
          callbacks: {
            label: function (tooltipItem, data){                                     //ここは描画後にマウスカーソルが乗った時の事前に内部でtooltipItemに定義された情報を基に値を返します。
                return pieChartData["datasets"][0]['data'][tooltipItem['index']] + "ポイント"       //77行目で定義されたデータから該当するデータを表示し
                  + "  キーワード → " + tooltipkeyword["type"][tooltipItem['index']]["title"];      //さらにキーワードとして109行目で定義された四つの傾向のタイトルを表示します。
            },
            afterLabel: function (tooltipItem, data){                                //172行目のラベルの後に別の情報を表示させるにはafterLabelツールチップコールバックが呼び出せます。
                  return "「" + tooltipkeyword["type"][tooltipItem['index']]["word"][Math.floor(Math.random()*(6-0)+0)] + "」";
            }                                                                        //さらにテキストとして109行目で定義された四つの傾向の文言のオブジェクトから乱数で選ばれた文言を表示します。
          }
        },
      }
    });
  });
})();

あとはこのJavaScriptファイルを

にのようにアップロードしていただければ。

どうでしょう。kintoneのデータにChart.jsを組み合わせるだけで、
kintoneのデータを分析することができてしまうのです。

Chart.jsにはさまざまなグラフが用意されているので、
kintoneの標準グラフでは表現できないことも可能です。

コーチング用の分析ツールとしても使えてしまうkintoneの奥深さを楽しんでいただけたらきとみちゃんも喜ぶはずです!
よかったら以下にChart.jsの公式サイトのリンクも貼っているのでご参考になさってくださいね。

当エントリの参考にさせていただいたブログ

  Topへ↑

最後になりましたが、このエントリ作成にあたり、以下の2サイトおよび、コーチングについて教えて頂いたお客様からの情報を参考にさせていただきました。ありがとうございました。

 Chart.jsドキュメント翻訳
 Chart.js公式サイト


kintone Café 東京 Vol.9を開催しました


11/15にkintone Café 東京 Vol.9を開催しました。

公式の開催報告はしかるべき場所に書かせていただきました。
こちら

関連するツイートのまとめサイトも作成させていただきました。
こちら

なので、ここでは代表である私が登壇の際に語った
kintone Caféとは?スライド
kintoneを簡単にご紹介スライド
Cybozu Days 2019のkintone周りをフィードバックスライド
をさらに補足するように、開催であらためて感じた思いを書かせていただきます。

3月末のkintone Café 広島に登壇した時、今までkintone Caféで話したことのなかった自治会を取り上げました。
今までは技術に即した内容を話すことが多かったのですが、技術に触れないkintone Caféの登壇が私に新鮮でした。

8月末に開催したkintone Café 東京 Vol.8 @多摩では、プロジェクト・アスノートの松田さんと共催しました。
それを機に私の話す内容を思い切って初期化し、kintoneを一から語ってみました。当然技術ネタは封印。

技術ネタだと、kintone Caféに来てくださった方がついて来れない可能性があります。当然、反応も薄くなります。
そもそもkintoneエバンジェリストとは、技術うんぬんではなく、kintoneの良さを広めることにあるのではないか。
私の中でkintone Café神奈川を何度か行う中で迷いが生じていましたが、直近の二回のkintone Caféで修正することができました。

今回のkintone Café 東京 Vol.9は開催要項にユーザー向けをうたっていました。
その一方で、今回の参加者の中には私の知る限り、かなりのスキルを持つ技術者も6,7名はいました。

そうした方々に対し、kintoneの初歩を語ることに意味はあるのか。
私はあると判断しました。
むしろ、技術者であるほど、kintoneが新鮮に映るはず。そうした意味でもユーザー向けの内容でよかったと思います。

今回、会場を提供してくださったのはクロス・ヘッド株式会社様。System Integrateの豊富な経験をお持ちです。
クロス・ヘッド様の会場をお借りしながら、技術に触れず、ユーザー向けの内容にすることに若干のためらいもありました。
ですが、kintone Caféを通しての皆様の反応は上々で、七割以上の方が懇親会に参加してくださいました。その事からも、ユーザー寄りで行く、との方向性は続けようと思いました。

私の登壇では、そもそもなぜkintoneをユーザーに勧めるのか、という観点で一生懸命語ってみたつもりです。
さらに、Cybozu Days 2019 in 東京で発表された内容を報告しました。

今回、一緒に登壇したkintone大好きキンスキ YouTuberの松井さんは、私のPCトラブルによる順番交代にも動じず、見事な登壇を務めてくださいました。そればかりかサイボウズさんならではの事例を提供してくださいました。

私に続いて登壇してくださった情報親方の東野さんは、Cybozu Days 2019で発表され、来場された方々に感心されたkintone導入ガイドブックの制作について、マニュアル制作のノウハウも惜しげなく披露してくださいました。

トリを務めてくださったTeruさんは、登壇を公開できないリスクを押してkintoneが最大に活きる業務改善の生の事例を語ってくださいました。kintoneの紹介から導入、そして業務改善効果に至るまで、今回のCaféを締めるにふさわしい内容でした。

あらためて、こうした地道な活動が、今後につながると確信できた1日でした。
12/7にはkintone Café JAPAN(サイト)が予定されています。そこでもきっと実のある内容が得られる事でしょう。
今回来てくださった35+αの皆様、登壇してくださった3名の仲間。会場を提供してくださったクロス・ヘッド株式会社様。皆さま本当にありがとうございました。


Cybozu Days 2019 in 東京に行きました


2019/11/7、11/8の幕張はある気配に包まれていました。
そこではCybozu Days 2019 in 東京が催されていたのです。
ただし、幕張に満ちていた気配とは、熱気ではなく禍々しい気でした。

今年のテーマは「モンスターへの挑戦状」
そのテーマ通り、モンスターたちに占拠されたパビリオン。会場全体が禍々しく彩られています。
その中にはちらほらと勇者のイラストも見受けられます。ですが、空間の大部分はモンスターに占められたまま。

そんな会場にも、参加者が増えていくにつれモンスターに立ち向かう機運が満ちていきます。
勇者とはすべての参加者のこと。皆でモンスターに立ち向かうのが今年のCybozu Daysのミッションなのです。

では、私たちが立ち向かうモンスターとは何でしょうか。
そのヒントは既にサイトにも動画で流されていました。そして、二日目のメインセッションでも青野社長から語られました。
モンスターとは目に見えません。そもそも存在すらしません。
モンスターとは私たち自身が作り上げてしまった存在なのです。

戦後の高度経済成長の結果、成功体験として日本中に刷り込まれてしまった観念。その代表が年功序列や終身雇用です。
そうした観念から派生したモンスターたち。
毎日決まった場所に通勤し、背広とネクタイの着用が正しいとする「通念」が蔓延り、その背後には一度きりの人生を「カイシャ」に捧げさせようと蠢くモンスターの闇が見え隠れします。

そうしたもろもろのモンスターたちと一緒に戦う私たちのために、七カ所に分かれたセッション会場のあちこちで、二日間にわたっていろいろなセッションが行われました。

初日のProduct Keynoteでは、GAROON、kintoneを軸に、これまでのアップデートと今後のアップデート予定が披露されました。
チーム応援ライセンスは対象アカウントが三倍に増え、危うく私の口から歓声が飛び出そうに。
LGWAN対応やアメリカでのkintone状況など、今後に期待が持てそうな発表が続きます。

keynoteの前には「俺キン」のセッションがありました。
俺はこんな風にモンスターと戦ってきた、と発表する勇者たちの姿は尊いです。私も場外から戦いの軌跡に聞き惚れました。
モンスターとの戦い方は勇者によってまちまち。あえてKINTONEに乗って登場する掟破りの登場もまたよし。

Keynoteの次は、kintone hiveです。
毎年聞いていますが、全国で知らぬうちに育ってしまったモンスターたちと戦う姿には、共感と感動があります。
例えるなら上出来のRPGのプレイ動画を大勢の勇者とともに観戦する臨場感のような。

私はhiveの前、各社のブースをダッシュで巡りました。
中でもfreee社のブースに長居していました。freee社で今度kintoneとfreeeのAPI連携についてお話しするためです。
その情報を入手し、freee社のみなさんと会話をして勉強。今後にもつなげていくためにも。

hiveの後は、kintone Café in Cybozu Daysにも参加しました。
Cybozu Daysが終わってすぐ、お膝元でのkintone Café 東京 Vol.9を主宰する私としては、全国各地にある勇者の集うギルドのあり方から学べるもの多かったこと。
kintone Café開催の意義や目的を考えるきっかけにもなりました。

このセッションと同じ時間には開発者向けのセッションがありました。
私はとても行きたかったけれど、kintone Caféのセッションを選びました。
まだまだ私もか弱い勇者なので、分身の術が会得できておらず悔しい。いずれ公開される動画を待ってみます。
なお、このセッションから冒険仲間に情報親方が加わってくれました。以後は行動をともに。

続いてはkintone hack Nightです。このセッションも見逃せません。
勇者として最前線に出てモンスターと戦うには、スキルを磨かねば。それを披露する場こそhack Night。
勇者の出で立ちに身を包んだ六名の勇者がおのおのの磨き上げたスキルを披露します。そして採点。
採点の間、魔の司会者ウッシーが召喚したモンスターが登場し、勇者にさらなる試練を与えます。あるモンスターは18世紀二ポーンの「コウメダユウ」に姿をかえ、華麗なる滑り芸を駆使して勇者に襲いかかります。あるいは21世紀の「ワタナベケン」がレスラーの出で立ちで絶叫して勇者の行く手を遮るのです。
勇者、危うし!
奮闘もむなしく、魔法に魅入られ笑わされた勇者には激烈なゴムパッチンが顔面に炸裂。容赦なくHPを削っていきます。

毎年、勇者の激闘から学ぶところは多いです。
私的には、AR世界の振る舞いをkintoneに結びつけた勇者「ウィル」さんのセッションがインパクト大でした。
が、優勝はヲタ芸とkintoneの組み合わせで満場の笑いをさらった勇者「つっきー」さんの逆転勝利。いや、楽しかった!

一日目『最軽量のマネジメント』を出版されたばかりのサイボウズ社副社長の山田さんがサイボウズ商店の店頭に立たれていたので、著書を購入し一緒に写真を撮っていただきました。
そして、情報親方と飯を食って締め。
ルーラを唱え、二時間以上かけて町田の宿屋に戻りまして。

さて、二日目。
いきなりの「SEKAI NO OWARI」の曲にバイオリンをフィーチャーしたバンドが登場し、素晴らしい二日目を予感させます。

二日目は、働き方をメインに据えたセッションが主です。
メインセッションでは青野社長の書籍「会社というモンスターが、僕たちを不幸にしているのかもしれない。」を編集したphp研究所の方と青野社長の対談から始まりました。
今回のCybozu Days 2019のテーマ「モンスターへの挑戦状」のもとになったというこの書を、私はまだ読めていません。ですが、この対談を聞くと、早く読まねば、という気にさせられます。
私が常々感じていた日本を覆う思い込みの数々。

タカマツナナさんとの対談も参考になりました。
お笑い芸人として活動する中、活動を若年層への政治参加を促すものに変えるなど、自在に活動を行うタカマツさんの意気込みが面白かったです。
タカマツさんもまた、芸人の立場から世の中のモンスターと戦っているのでしょう。

そのタカマツナナさんの後に登場したのが麹町中学校の工藤校長との対談です。
工藤校長は、宿題や委員会など学校生活につき物のさまざまな制度に抜本的な改革を加えたことでよく知られています。
一人一人の生徒のことを考えた結果、そうした制度や行事で必要のないものは廃止も辞さない。そうした伝統的に伝わる制度の多くは、実は前例主義に基づいた思い込みのモンスターの生み出した幻想にすぎない。そう力強く言い切る工藤校長こそ、教育界にさっそうと現れた勇者の中の勇者なのかもしれません。

この日の私は、作業もしなければならず、昨日以上にブース巡りができませんでした。用意してくださった電源席で、作業に没頭。

続いてのセッションはサイボウズ社に中途入社した社員が語る、サイボウズ社内の情報共有のあり方です。
私もお客様とともにモンスター退治の旅に出る時、早い時期にこのモンスターと戦います。
そもそも、情報共有に妙なブロックがあると、kintoneという武器もうまく機能しません。
どこまでの情報をkintoneにアップしてもらえるのかによって導入の成否も分かれるのではないか。そう思います。

その視点でみたこのセッションはまさに驚きです。
社内経営会議の生の声まで含めて全社員に公開するというのですからすごい。
ですが、私も妙な秘密主義は面倒なので、この点はとても勇気づけられます。
また、経営者の方にもサイボウズさんはここまでやってますよ、という根拠を得られたことも大きかった。

続いてのセッションは医療介護業界の四天王がパネリストとなって、kintone導入のあれこれを語ってくださるセッションです。
四天王の内、三人はCybozu Days 2019の前日にkintone エバンジェリストに就任した方で、まさに豪華な陣容です。
実際、医療業界へのクラウド導入は、クラウドの認知のバロメーターです。
妻が歯科診療所を経営している私にとっても、とても興味深いセッションでした。

その次のセッションも、クラウドの認知のバロメーターの一つです。
自治体への導入にあたっては、LGWANとの連携がセットでないと情報の流通が滞ります。
神戸市と市川市の事例ですが、かつて神戸市役所と芦屋市役所で入力オペレーターをした経験がある私にとって、神戸市の事例には興奮しました。神戸市が主宰するワークショップには芦屋市の方も参加されているとか。
私の来し方を考えると感慨が湧きます。この分野でも今後は協力をしていければうれしいと思いました。
「来なくてもいい市役所」の実現に向けて。

そしてすでに最終セッションへ。
その前に、アーセスさんのブースに立ち寄りました。
お客様がチーム応援ライセンスに申請していて、申請が通ればアーセスさんのプラグインを使う提案もお客様にしていました。なので、KOUTEIやKOYOMIについてあらためてお話を聞きに伺ったのです。

最終セッションは『五体不満足』で知られる乙武氏の講演。そして青野社長との対談です。
乙武氏もまた、世に跋扈するモンスターと戦い続けてきた勇者です。とにかく目の力が強く、意思のみなぎった面構え。表情は豊かに、雄弁に語ってくださいます。不満足な五体で表現できない分を言葉と顔と喉だけで。
私も話者の端くれとしてとても刺激を受けました。スピーカーたるものこうありたいものです。
美しいものは、と問われた乙武氏が「覚悟」と答えた姿が、まさに美しい。モンスターと戦い続けるには覚悟がいります。その覚悟こそ、勇者のまとうべき究極の鎧なのかもしれません。
著書『四肢奮迅』を出されたばかりだというので、私も手に取ってみようと思いました。

冒険の旅に終わりはありません。
とはいえ、四人の勇者と幕張で飲みかわし、いったん私にとってのCybozu Days 2019 in 東京は幕を閉じました。
三日続けて幕張と町田を往復したのでHPとMPはすっからかん。でも、その替わりKP(気力)は満タンに充填されました。

初日の禍々しい気配からは一変、幕張の夜にはさわやかな風が吹いていました。ここ、幕張の地からモンスターは退けられたのです。

ただし、RPGの世界ではラスボスを倒してもゲームは永遠に続いていきます。私たちの人生もそうであるように。
まだまだモンスターは世に棲んでいるのです。

そうしたモンスターを退治する冒険の旅路では、仲間との交流が欠かせません。
今回、こうした英雄たちの交流の場を作っていただいたサイボウズさんには感謝です。
スタッフの皆様、登壇者の皆様、参加者の皆様、ありがとうございました。
これからもモンスターと戦い続けていきましょう!


kintone EvaCamp 2019に参加しました


令和元年11月6日。
幕張の某ホテルに代表の私はいました。
翌日から二日間にわたって開催されたCybozu Days 2019 東京を控え、kintone エバンジェリストが集うパーティーに参加するためです。

会場の窓からは、美しい光景が広がっていました。
夕日の色合いを背後に黒くそびえる富士山。
足元にはZOZOマリンスタジアムがまばゆく輝いています。
マリンスタジアムのそばには、明日からの祝祭に備え、ひっそりとうずくまる幕張メッセが。
素晴らしく印象に刻まれる光景でした。

その日の様子は
こちら
の記事にアップされています。
まさに、Cybozu Days の前日にふさわしく、意気みなぎる時間でした。
すばらしい場所と時間をご用意くださったサイボウズ社の皆様には感謝です。

サイボウズさんは、今までもCybozu Daysの前日にはこうした場をご用意してくださっていました。
そして、私も時間の許す限り参加してきました。
ところが、今回のEvaCampはその中でも特に意義あるものです。
今までは、サイボウズ公認 kintone エバンジェリストといっても、お墨付きサイトがありませんでした。
なので、私たちエバンジェリストは自分で発信するしかエバンジェリストを認知してもらうすべがなかったのです。

今回、公式サイトが設けられ、エバンジェリストが名実ともに認められたことは大きな変化といえましょう。
こちらに一覧が

2014年8月よりkintone エバンジェリストに任命された私。
すでに5年の月日が経過しました。
ところが、最初の二年間の私のエバンジェリストとしての活動は、自他ともに満足のいくものではありませんでした。

Cybozu Daysですらかろうじて一日、しかも数時間参加できる程度。
他のあらゆる展示会には参加すらできませんでした。
もちろん、新規kintone案件も数える程度。
cybozu developer networkに記事を書き、毎年年末のkintone advent calendarに書き、たまにkintone Café を開き、登壇する程度。

そんな私は、2017年の秋に常駐現場を卒業し、すでに2015年の春に果たしていた法人化もあいまって、ようやく活動できる時間を手に入れられました。

おかげさまで昨年の秋辺りからkintone案件が増え、今では社業の6~7割を占めるまでになっています。そして毎日、kintoneのことを考えています。

今回、kintone エバンジェリストとして継続するかどうか。決断を求められましたが迷う余地はありません。速攻で継続を決意しました。
出だしの二年の空白を埋めるためにも。
その間、活動の鈍い私に業を煮やしながらも見放さないでいてくれたサイボウズ社の担当者様のご厚意に報いるためにも。
そして、私自身が望むワークライフバランスのためにも。

世にいろいろなサービスはありますが、私にとってサービスの背景にある哲学に最も共感できるサービスがkintoneなのです。
このサービスを提案し、使ってもらうことで社会になにがしかの貢献ができていると思える。
それって働き方を考える上でとても大切だと思えます。特に技術者には。

まだまだ世の中には固定観念という目に見えないモンスターが跋扈しています。
それを一つ一つなくしていくために、私は求められる限り、kintoneを薦め、広めていこうと思います。


kintone Café 東京 Vol.8 @多摩を開催しました


8/30にkintone Café 東京 Vol.8 @多摩を開催しました。

公式の開催報告はしかるべき場所に書かせていただきました。(こちら

ツイートのまとめサイトも作成させていただきました。(こちら

なので、ここでは代表である私が登壇の際に語った「スライド」をさらに補足するように、開催であらためて感じた思いを書かせていただきます。

本稿を書こうとして、前回アクアビット長井として主催したkintone Caféを調べたところ、2017年3月のkintone Café 神奈川 Vol.5までさかのぼることがわかりました。つまり、前回の開催から2年半、kintone Caféを主催していませんでした。空きすぎです。間を空けすぎたことに忸怩たる思いです。

2年半の間、もちろん手をこまねいていたわけではありません。ツイートもたくさんつぶやきましたし、kintone Advent Calendarにも毎年書いています。DevRelにも参加し、弊社ブログ記事にもkintoneのことは書いています。他のkintone Caféでは登壇もしましたし、お客様の主催するセミナーでも登壇もしました。代表がエバンジェリストとして全く何もしていないとは思いません。kintone Café神奈川もなんどか話を持ち掛けては立ち消えを繰り返し、それなりの開催に向けての準備は進めました。

でも、結局は開催できなかったことに変わりありません。kintone Caféに限らず、セミナーは自らが行うべき。そう思っています。特にkintone開発が弊社の業務の主流になっている今では、一日のほとんどをkintoneの事を考えていたわけですから。これは怠慢と言われても仕方ありません。

何が原因だったか。結局、代表自身がkintone Caféのテーマについて、どう開催するか迷いが生じていたの正直なところです。技術寄りの内容で開催するのか、ユーザー寄りの内容にするのか。技術寄りにしたところで、どこまで伝わるのか。スキルは座学やハンズオンでどこまで伝えられるのか。そもそもkintoneエバンジェリストとは、技術うんぬんではなく、kintoneの良さを広めることにあるのではないか。私の中でコンセプトが右往左往していました。

今回、弊社のサテライトオフィスで開催したのは、オフィスの大家さんから開催要望があったからでした。そして要望の中で「そもそも論」を聞きたい、ということでした。つまりkintoneとはそもそも何か、という地点から話を起こす必要がありました。都合のよいことにkintone Café東京を今運営しているメンバーの松田さんはkintoneを軸とした業務改善を推進されておられます。そうした風もあり、私の登壇資料も技術のことは触れず、ユーザー向けの内容にしてみました。

今回のkintone Café 東京 Vol.8 @多摩は開催要項にユーザー向けをうたっていたこともあり、6,7名はkintoneについて触ったことがなく、そうした意味でもユーザー向けの内容でよかったと思います。私の登壇でもそもそもなぜkintoneをユーザーに勧めるのか、という観点で熱く語りました。他の方のスライドも、そうした点でテーマが絞られていたようです。

今回、久しぶりに主催してみて、ユーザー寄りで行く、との方向性は続けようと思いました。ただ、ハンズオンはやれればやりたい。一緒に登壇したエバンジェリストの新妻さんからも、「データ」「プロセス」「コミュニケーション」の三つの柱で語るのは、初期のkintoneのコンセプト。今や概念で語るのではなく、直接ハンズオンで魅力を伝えることの必要性を説かれました。仰る通りで、次回はハンズオンにも取り掛からねば、と思います。

今回、一緒に登壇した情報親方の東野さんも、これからkintoneが導入されていく中で必ず使われるであろう資料を軽く紹介してくださいました。やはりユーザー導入の推進こそが鍵なのでしょう。

お客様より恵比寿と武蔵小杉でもkintone Caféを開催して欲しいというご要望をいただいています。具体的に会場も確保できています。今年はあと二回、開催できればと思います。その時私が、どこまで技術を語りたいという誘惑に耐え、ユーザー目線の内容に踏みとどまれるか。肝に銘じたいと思います。まずはkintone Caféを主催できる自信を取り戻せたことが、今回、私の中で最も得難い収穫だったと思います。

今回来てくださった14名の皆様、登壇してくださった3名の仲間。皆様、本当にありがとうございました。


kintone Café 広島 vol.12 @福山に登壇しました


3月1日に開催されたこちらのイベントに弊社代表である私長井が参加し、登壇して参りました。

昨年秋のkintone Café Japanの懇親会で結ばれた今回のご縁。今年に入ってから示されたCaféのお題は「小さい組織でのkintone活用術!」だとか。
https://kintone-cafe-hiroshima.connpass.com/event/119652/

であらば、私が話すべきテーマは自治会しかあるまい、と思いました。昨年六月、サイボウズ社においてチーム応援ライセンスの記念セミナーに登壇しました。その際に取り上げた主な話題はCybozu LiveからCybozu Officeへの移行。kintoneにはわずかしか触れられませんでした。ところが、自治会業務にとってkintoneは欠かせないサービスなのです。ならば、今回のkintone Café 広島は私が思い描くテーマを深める良い機会となるはず。

今回、参加者の中にはkintoneを触った事のない方もいらっしゃるとか。であるなら、私は技術に深入りすまい。kintoneを導入するにあたっての前提だけを語ろうと思いました。自治会の業務は、他の小さい組織のモデルケースにもなるだろうし。技術的な観点や実際のkintoneの使用デモは、他の登壇者久米さん、山川さんが触れてくれるはず、との期待をこめて。

そんな思いで資料をまとめ始めたものの語るべきことは多い。チーム応援ライセンスの概要にも触れておきたい。そして私に与えられた時間は二十分。なのにスライドの枚数はどんどん増えてゆき、ついには六十枚を突破。

福山駅から久米さん夫妻とタクシーに乗り、会場である猪原歯科さんについたのは開演15分前。入るなり、中の洗練された様子に感嘆することしきり。私の妻も歯科診療所を経営しているのですが、規模が違いすぎます。副院長である猪原健先生が自らご案内してくださるという光栄にも恵まれまして、私たちは数名で院内を見学させていただきました。ただただ感嘆しながら。

うちの妻の歯科診療室はユニット一台。妻が受付と医師も兼ねています。ところが猪原歯科さんはパーティションで区切られた四つのユニットと三つの個室診療室を擁しています。さらには内科診療室や言語療法室なども備わっており、そのすべてが駐車場から完全バリアフリーというのがすごい。入り口の待合室のソファーも素晴らしいし、何よりもオープンキッチンの存在が異彩を放っています。このオープンキッチンは「摂食・嚥下リハビリ中の患者様・ご家族様に対して、嚥下食や高カロリー食の調理方法等をレクチャーするキッチン設備」と謳われています。私は今までも妻の関係からあちこちの歯科医院に訪れましたが、オープンキッチンが備わった歯科診療所は初めてみました。さらに、最新型のデジタル印象採得からセラミックの最終修復物まで作り上げてしまうという優れモノの機械が鎮座し、私はただただ感嘆の言葉しか出ません。歯科技工士さんも院内で常駐されており、猪原歯科さんが院内の管理にkintoneを活用されている理由がわかった気がします。

猪原歯科さんは1946年に設立してから70年以上の歴史を持っているとのこと(サイト)。妻の実家の歯医者も借地にさえしていなければ60年の歴史は誇れていただろうに。とその解体を見守った私に、複雑な思いが去来しました。

さて、kintone Café 広島 vol.12の開催です。安藤さんの司会によって始まり、トップバッターは私。安藤さんが今回の参加者の皆様にkintoneの認知度を伺ってくださったところ、技術面で深入りしないほうがよい、との判断は当たったようでした。ところが、しゃべっているうちに話し始めた時間を忘れてしまい、なんとか60枚近くのスライドを語ったものの、時間を超過するという凡ミス。冒頭のアンケートでkintoneをほぼ使ったことのない方が多かったため、予定していなかったアプリのデモにまで話を広げたのはやりすぎでした。

まあ、笑ってもらいたいところで受けがとれたのは自分の中で認めてもよいかも。あと、昨年のチーム応援ライセンスの記念セミナーで登壇した際も思ったのですが、自治会というネタは刺さる人とそうでない人に分かれます。今回は数人の方が熱心にうなづいてくださっていました。ただ、そうでない方は反応が薄い。私の反省点として、自治会という対象は中小企業にも通ずる、という論点をもう少し強調するべきでした。
https://slides.com/yoshikazunagai/kintonecafe_hiroshima_vol12/fullscreen

私の後に登壇してくださったのは福岡からご参加の久米さん。スペースの使い方、というテーマで、マルチアカウントを操りながらのコメントや通知のやりとりの実例を披露していただきました。コミュニケーションこそが重要との私の主張を深めてもらい、皆さまもよりご理解が進んだのではないかと。私自身、今後の登壇では一人で全部こなす可能性もあります。そのためにもマルチアカウントを駆使したデモは習得しないと。
https://www.slideshare.net/jkume/20190301-kintone-caf-hiroshimavol12

さらに続いては岡山からご参加の山川さんによるウェブ連携のデモ。kintoneの内容をwordpressなどのCMSに表示させる実例をシンプルな例で説明してくださいました。実はデータベースだけの導入でもkintoneは業務改善に大きな効果を発揮します。それがシンプルな実例として見られたのは良かった。私も実装経験はあり、記事にも書いた事があります。サイト内のカタログが簡単に修正できるデモは、参考になりました。
https://www.slideshare.net/secret/9hlG9PwUCFI2cM

私が伸ばしてしまった時間を後のお二人がきっちり時間内に収めたのはさすが。とっても感謝です。

最後に質疑応答の時間では、私が触れたポータルのカスタマイズなどにご質問を頂戴しました。ポータルの整備こそ、リテラシーの壁を越えてkintoneを広めるための要点という私の見込みに自信が持てました。
https://www.kintonecafe.com/activities/4327!

夜の懇親会では、広島といえば海の幸、という事で楽しい会話においしい料理とお酒に癒やされました。kintone Caféの懇親会は毎回、楽しみなのです。

ご参加の皆様、運営の皆様、猪原歯科の皆様、誠にありがとうございました!


EBISU Tech Night Vol.5にて登壇しました


昨日、EBISU Tech Night Vol.5 で登壇を求められ、喋ってきました。
お題は「kintone + MONACAでモバイルアプリ開発!」
まあ、タイトルはストレートです。

EBISU Tech NightはAWSやcybozu.comの凄腕技術者が集うイベントです。今まで私はVol.2でも、Vol.4でも登壇の機会をいただきました。今回が三度目です。

今回はモバイルアプリ開発というお題で挑んでみました。AWSやcybozu.comを主戦場とする技術者とはいえ、ほとんどの方が開発するシステムはPCで使用するシステムのはず。モバイルアプリはあまり経験されていないのでは。そんな予測で臨んだ今回でしたが、冒頭で「MONACAを知っているでしょうか」と三十名程の聴衆者に伺ってみたところ、ご存じなのはお一人だけでした。手を挙げなかった方を含めても一割ほどの認知でしょうか。この分野でメジャーなXamarinを例に挙げたとしても同じ程度だったかもしれません。

モバイルアプリ開発への認知度がその程度とはいえ、今後もモバイルアプリが求められる確度はかなり高い。これに異をとなえる方はそうはいないはず。モバイルの処理能力が飛躍的に向上した今、PCのアプリに対し優位に立てるのは画面の大きさと、複数アプリの同時表示という点ぐらい。

ところがモバイル開発は技術者にとって鬼門でした。少なくとも私には。かつてのフィーチャーフォンでどれぐらい苦労したか。それこそが環境差異。かつてのフィーチャーフォンが廃れてしまった一因には、キャリアごとに開発環境や言語が違ったため、アプリの開発工数がかかった事情も無視できないと思います。そして今もまた「iOS」と「Android」の間でシェアを巡る争いとバージョンアップによる無数の環境が生まれています。

わたしはこうした環境差異を吸収するために費やす時間は無駄だと考えています。もっと機能そのものをビジネスにフィットさせるべきだろうと。そのためにもMONACAのような簡単に環境の差を乗り越えてアプリをビルドするツールはもっと世に知られるべきだと。

今回はkintoneから取得したデータを元にアプリの画面を描画し、最後はkintoneにデータを書き込むところまでお見せしました。モバイルアプリのデータベースは、Local StorageやSession StorageやWeb SQLやIndexed DBやSQLiteが主流です。が、kintoneを使うことで、データベース周りが簡単に作りこめます。kintoneに加え、なおかつモバイルアプリも簡単に作れる。私が示したかったこととはそれです。

そうした意図で語った一部始終、どこまで皆さんに伝わったかはともかく、環境の差異を回避する策の一環として伝わったのであればひとまずは成功です。惜しむべきは、笑いを織り込むまでにスライドを練られずに壇上に上がったことでしょうか。

来年も引き続きEBISU Tech Nightは行われるとか。すでに司会の方から四回目の登壇のご依頼もいただきました。引き続き精進しつつ、皆さんを引き付けられるような登壇を目指します。

スライド


ライフログのkintone盛り alasql仕込みのGoogle Chart添え


1.読まなくてもいい献立の前書き

  目次へ↓

唐突ですが皆さん、ライフログって言葉覚えてますか? 何!忘れた? そんな言葉あったっけ?

それならば説明しましょう!システムが行った作業の結果がログ。ウェブ上にログを残すからウェブログ。略してブログ。そして、人生のイベントを記録するのがライフログ。今やこの言葉は、数多くの新語とともにハードディスクの肥やしと成り果てています。

ところが、まだ忘れるのは早い!という訳で、kintoneでライフログです。

このライフログという概念は2000年代の初め頃に産声をあげていたといいます。ITジャーナリストの佐々木俊尚氏は、2010年の秋に発表した『キュレーションの時代』の中でライフログの概念は2010年代の間にはまだ根付かないだろう、と的確に予言されています。なぜなら、日本にはプライバシーの公開に気持ち悪さを持つ人が大多数だから、と。つまり、ライフログとはまだこれからの概念なのです。佐々木氏はその本の中で、ライフログを集めるツールとしてFourSquare_logoをプッシュしています。このFourSquare_logoですが、ある時期に仕様が迷走したこともあって日本ではすっかり影を潜めてしまいました。ですが、海外では位置とモノを関連付けたデータをがっちり握った企業として存在感を保っているようです。FourSquare_logoとは、一言でいうと訪問場所でチェックインすることで自分がいつどこにいたかを後世の自分、またはどこかの物好きのために記録しておくツールです。それはまさにライフログ。なお『キュレーションの時代』はかつて弊社のブログでも取り上げています。

kintoneは言うまでもなく優れたプラットホームサービスですし、仕事の改善にはテキメンに効きます。それはもう間違いない。でも、プライベートな使い方だってできるのですよ。ワークライフバランスを充実させるにはプライベートでもkintoneを使い倒したほうがいいと思いませんかみなさん!?

私にとって幸いなことにkintoneFourSquare_logoの連携事例はウェブにほぼ皆無です。そこは青い大海原、ブルーオーシャン!というわけでささやかですが、連携事例を公開したいと思います。あわせて、これもkintoneとの連携事例が極めて少ないGoogle Chartと絡めて。題して「ライフログのkintone盛り alasql仕込みのGoogle Chart添え」

2.ご用意いただく材料

  目次へ↑

ちなみにこのレシピの内容を再現せんと志す奇特な方は、以下のものをご用意ください。

  • FourSquare_logoのアカウント(チェックイン用のアプリSwarm_logoも忘れずに)
  • kintoneのスタンダードプラン
  • phpが動き、cronが実行できるサーバー(レンタルサーバーでも可)
  • Google Maps Platform上で動くライブラリを呼び出すためのAPI KEYの入手。こちらをご参考に

このレシピは2018年12月時点の「さくらのレンタルサーバー スタンダード」(phpは7.2.10(CGI版))で動作することを確認しております。
また、Google Maps Platformで動くAPIについては、月あたり40000リクエストまでが無料枠内だそうです。このレシピはプライベート利用なので、そこまでいかないことを念頭においています。
ちなみに、みんな大好き「zapier」でも、Swarm_logoでチェックインする度にkintoneに新たなレコードを追加できます。が、それをいっちゃぁおしめえよ、ということでお付き合いいただければ幸いです。
このレシピは一日単位でkintoneにライフログを一括で登録することに意義を持たせています。そして激動の一日を振り返りつつ、自分をねぎらいたい、そして昨日を忘れ去りたい「あ・な・た」の味方になることも!

なお、いうまでもなく、ライフログはみだりに公開するものではありませんぞ。このレシピのせいであなたのデイリーライフに何か問題が起こっても責任は取れませんのであしからず〜

3.献立の出来上がりイメージとレシピ

  目次へ↑

FourSquare_logoはAPIがOAUTH付きで公開されています。なので私の策略はこうです。まず、午前0時を過ぎたあたりでFourSquare_logoの前日分のチェックインデータをkintoneにシュっと放り込んでやろう。そして溜まったデータをウヒャヒャと一覧で眺めてみよう。ついでに今年、どんぐらい国や県を訪れたんやろう、と地図に出して一人ニヤけて悦にいる。うーん、ダークロースト。

まずは着地点をお見せします。上が世界の長井。下が日本の長井です。

世界の中心で長井を晒す

日本の中心で長井を晒す

ともにkintoneのカスタマイズビューではなく通常の一覧を使って長井を晒しています。世界地図は日本と台湾に色がついており、日本地図は東京が最も濃く、次に神奈川。データはFourSquare_logoSwarm_logo)でチェックインしたデータをphpでkintoneに取り込みます。地図を書き込むのは一覧画面のヘッダスペースです。そのため、世界地図版と日本地図版で一覧を分けています。

kintoneのアプリの構造はこんなんです。

フィールド名 フィールドコード
場所 場所 文字列(1行)
文字列(1行)
都道府県 都道府県 文字列(1行)
コメント コメント 文字列(1行)
日時 日時 日時

あと、アプリの設定画面で下図の3つのファイルを登録してください。
https://www.gstatic.com/charts/loader.js
とhttps://cdnjs.cloudflare.com/ajax/libs/alasql/0.4.11/alasql.min.js
はCDNから使います。最後のjsファイルはこの後説明します。
kintone_app

ここであらためて全体の構成図をご覧いただきましょう。一目瞭然とはこのことですね。

構成図

4.FourSquareの下ごしらえ

  目次へ↑

日々のチェックイン履歴をFourSquare_logoに溜める方法は、簡単に書くとこんな感じです。

  • Swarm_logoのアプリを立ち上げ、チェックインしたい場所でチェックインボタンを押す。
  • Swarm_logoの候補の一覧にその場所が登場します。
  • 登場しなくても検索すれば場所が指定できます。
  • 指定した場所でチェックイン(構成図の①)を行えば時間とともに自動的に記録(構成図の②)されます。気が向けばコメントも入れられます。

どうです?モンスターボールを投げなくてもよいし、ポータルを三角形で囲わなくてもよいのです。簡単ですよねFourSquare_logo

さて、今回の記事ではAccessTokenをFourSquare_logoから取得する処理(構成図の③)から説明します。AccessTokenはこのレシピの中で一度取得するだけです。なぜ一度だけでよいかというと、AccessTokenはユーザーを特定するのに使われるからです。つまり、一度AccessTokenを取得しておけば、何度でも使いまわしができるのです。FourSquare_logoには他にもたくさんのAPIが用意されています。処理によってはその都度AccessTokenを取らねばならず、ソースも複雑な下ごしらえが求められることもあります。ですが、このレシピではユーザーのチェックイン情報だけをとるのが目的なので、一度きりでよいのです。

5.FourSquareのAPI KeyとAPI Secretを取り寄せる

  目次へ↑

まず、https://ja.foursquare.com/にアクセスしてください。

FourSquarePage

そして、右上の開発者をクリックします。

すると、https://developer.foursquare.com/にページが遷移します。

FourSquareDeveloperPage

がでたら、右上のCreate Accountからアカウントを作成してください。

多分、FourSquare_logoで作ったアカウントがそのまま開発者アカウントとして引き継がれるはずです。

そしたら下図のようにMy Appsを選びましょう。アプリの作成のリンクがあるはずです。

FourSquareMyApp

こんな風に入れてください。

FourSquareMyAppRegist

この中で重要なのは3点。

  • Client ID、Client Secretは次の作業で使うのでちゃんとメモっておきましょう。
  • Client ID、Client Secretは墓まで持っていきましょう。人に教えちゃダメ!
  • Redirect URI (s)もちゃんと考えておきましょう。これは次の作業でこさえるスクリプトのURIです。

Application Urlという項目もありますが、ここは適当でよいです。他の項目も商用で使わなければ空白でよいはず。

6.FourSquareのアクセストークンの取り寄せとソースの仕込み方

  目次へ↑

続いて、アクセストークンを取得するためのphpを示しましょう。98行。38ステップです。

コメントに内容は記載しているので、まずはソースを味わってみてください。

設定A ⇒ 条件分岐B ⇒ 条件分岐C ⇒ 処理D ⇒ 処理E ⇒ 処理F ⇒ 条件分岐G ⇒ 処理H ⇒ 条件分岐I ⇒ 処理Jの順に進みます。

  // 設定A //
  // マイアプリで表示されたClient ID
  $client_id = 'KOKONIHAHONMAYATTARARANDAMUNAMOJIRETSUGAHAITTORUNEN';
  // マイアプリで表示されたClient Secret
  $client_secret = 'SEYAKEDOKOKONISOREDASHITARAORENOKOUDOUGABARERUKARADASAHENNEN';
  // リダイレクトURL (このスクリプト自身のアドレスです)
  $redirect_uri = 'https://*****.*****.ne.jp/*****/****_*********.php';
  // アクセストークン取得URLのベース
  $access_token_baseurl = 'https://foursquare.com/oauth2/access_token';
  // 認証URLのベース
  $authenticate_baseurl = 'https://foursquare.com/oauth2/authenticate';

  // 結果表示HTML用
  $html = '';
  // 結果表示見出し
  $html .= '<h2>実行結果</h2>';

  // 条件分岐B //
  //初回は$_GET['code']がなく中の処理は実行されない。「許可」され、リダイレクトされた場合に実行
  if( isset( $_GET['code'] ) 
    && !empty( $_GET['code'] ) 
    && is_string( $_GET['code'] ) ) {
    // 処理E //
    // 認証画面でFourSquareの実行が許可されると$_GET['code']付きでこのスクリプトが呼び出される。
    // アクセストークンの取得に利用するコード
    $code = $_GET['code'];
    // 処理F //
    // アクセストークン取得のためのパラメータを設定したUrlを組み立てる。
    // リクエストURL
    $request_url = 
      $access_token_baseurl . 
        '?client_id=' . $client_id . 
        '&client_secret=' . $client_secret . 
        '&grant_type=authorization_code' . 
        '&redirect_uri=' . rawurlencode( $redirect_uri ) . 
        '&code=' . $_GET['code'] . 
        '&state=users/self';
    // curlを初期化する
    $curl = curl_init();
    curl_setopt( $curl , CURLOPT_URL , $request_url );
    curl_setopt( $curl , CURLOPT_HEADER, 1 );
    // 証明書の検証を行わない
    curl_setopt( $curl , CURLOPT_SSL_VERIFYPEER , false );
    // curl_execの結果を文字列で返す
    curl_setopt( $curl , CURLOPT_RETURNTRANSFER , true );
    // タイムアウトの秒数
    curl_setopt( $curl , CURLOPT_TIMEOUT , 5 );
    // 実行し、結果を$jsonに代入
    $res1 = curl_exec( $curl );
    $res2 = curl_getinfo( $curl );
    curl_close( $curl );
    // 取得したJSONデータ(ヘッダーサイズでTrimしないとFourSquareはエラーになる)
    $json = substr( $res1, $res2['header_size'] );

    // JSONをオブジェクト型に変換する
    $obj = json_decode( $json );

    // 条件分岐G //
    if( !isset( $obj->access_token ) ) {
      // アクセストークンを取得できなかった場合
      $error = 'アクセストークンを上手く取得することができませんでした。';
    } else {
      // 処理H //
      // アクセストークンを[$access_token]に代入する
      $access_token = $obj->access_token;
      // アクセストークンをブラウザーに出力する
      $html .= '<p>取得したアクセストークンは <b><mark>' . 
        $access_token . '</mark></b>です。</p>';
    }
  } elseif( isset( $_GET['error'] ) ) {
    // 「拒否」して返された場合怒る。
    $error = 'なんで「許可」してくれへんの!?';
  } else {
    // 条件分岐C //
    // 初回はこの処理が行われるはず。
    header( 'Location: ' . $authenticate_baseurl . 
      '?client_id=' . $client_id . 
      '&response_type=code' . 
      '&redirect_uri=' . rawurlencode( $redirect_uri ) );
    // 処理D //
    // headerの後はexit()
    exit;
  }

  // 条件分岐I //
  // エラー判定
  if( isset( $error ) && !empty( $error ) ) {
    $html .= '<p><mark>' . $error . '</mark>' . 
      'もう一度、認証をするには、' . 
      '<a href="' . explode( '?' , $_SERVER['REQUEST_URI'] )[0] . '">こちら</a>' . 
      'をクリックして下さい。</p>' ;
  } else {
    // 処理J //
    // ブラウザーに[$html]を出力 (結果としてアクセストークンの文字列が表示されます)
    echo $html;
  }

もう一度上の処理をおさらいします。
・最初にこのスクリプトが呼ばれた際、URLにcodeパラメーターはついていません。
・だからBの条件でEFGHの処理は行われず、条件分岐Cの処理が実行されます。
・その中ではLocationヘッダを送信しています。なので、その下の処理Dでただちに認証画面にリダイレクトされます(認証画面は割愛します)。
・そこで認証が終われば、再びこのスクリプトがcodeパラメーター付きで呼ばれるよう、処理DのLocationヘッダの中にredirect_uriパラメーターを指定しています。
・再びこのスクリプトが実行されると、今度は条件分岐Bで処理E、Fが実行されます。
・FではFourSquare_logoのアクセストークンを返すAPIが呼ばれます。
・なので、無事にアクセストークンが返ってくるという流れです。お判りでしょうか?

そしたらこのスクリプトファイルをどこかのウェブサーバーに送ってください。phpファイルの実行権限付きで。先ほどFourSquare_logoのマイアプリの設定でRedirect Url (s)に入力していただきましたが、その内容と合わせておいてください。

たとえばhttps://hanamogera.com/mokeke/foursqaure_tokun_yokose.phpに送るとします。そしたらブラウザーのアドレスバーに直接上のアドレスを指定し、実行します。

すると結果が以下のように表示されるはずです。このマーカー部分をコピーしておきましょう。

アクセストークン

おめでとうございます。アクセストークンはこれであなたのものです。ついに調理人として存分に腕を振るう時が来たのです!!

7.FourSquareからCheckinデータの取り寄せとソースの仕込み方

  目次へ↑

続いては、いよいよAccessTokenを使ってFourSquare_logoにデータくれ!とおねだり (構成図の④) してみましょう!もらったデータをkintoneにシュッと投げ込むまで(構成図の⑤)のphpも説明します。まずはphpを以下に示します。115行。64ステップです。

こちらもコメントに内容は記載しているので、まずはソースを味わってみてください。
先のスクリプトはソースに大分シェフの手を加えましたが、このスクリプトは素材の味を生かすような作りにしています。

設定A ⇒ 設定B ⇒ 設定C ⇒ 設定D ⇒ 設定E ⇒ 条件分岐F ⇒ 条件分岐G ⇒ 処理H ⇒ 処理I ⇒ 処理J ⇒ 処理Kの順に進みます。

  // 設定A //
  // アクセストークン (前の処理で取ってきた文字列です)
  $access_token = 'YATTAYOAKUSESUTOKUNTORETAYOKOREDESIAWASENINARERUNE';

  // 設定項目 (ここはアクセストークン以外はデフォルト)
  $params = array(
    'oauth_token' => $access_token ,// アクセストークン (これでユーザーが認識される)
    'locale' => 'ja' ,              // ローカライズ (jaは日本)
    'm' => 'swarm' ,                // モード (foursquare OR swarm)
    'v' => '20150801' ,             // バージョン (APIのバージョン。今のところ左の年月日)
    'limit' => '250'                // 取得件数 (250が上限)
  ) ;
  // 設定B //
  // GETメソッドで指定した場合 (設定項目のパラメーターを差し替える)
  foreach( array( 'locale' , 'm' , 'limit' , 'sort' , 'afterTimestamp' , 'beforeTimestamp' ) as $val ) {
    if( isset( $_GET[ $val ] ) && $_GET[ $val ] != '' ) {
      $params[ $val ] = $_GET[ $val ] ;
    }
  }

  // 設定C //
  // 設定項目 (日付や並び替えなどの条件を追加します。例えば2018/11/30の00:15時点で実行されたとします。
             するとstrtotime("today")は2018-11-30 00:00:00が返されます。
              strtotime("yesterday")は2018-11-29 00:00:00が返されます。
              それをもとにした$params["afterTimestamp"]は2018-11-29 00:00:00より後のチェックインデータを、
              それをもとにした$params["beforeTimestamp"]は2018-11-30 00:00:00より前のチェックインデータを取得します。
              $params["sort"]はoldestfirstを指定すると上記の日付範囲のチェックインデータのうち古いものを最初に取得します。
              ここ重要です。kintoneライフログ調理師試験に出ますので、要おさらい!!) //
  $today = strtotime("today");
  $yesterday = strtotime("yesterday");
  $params["afterTimestamp"]=$yesterday;
  $params["beforeTimestamp"]=$today;
  $params["sort"]="oldestfirst";

  // リクエストURL (usersの次のselfは固定文字)
  $request_url = 'https://api.foursquare.com/v2/users/self/checkins?' . http_build_query( $params );

  // 設定D //
  // cURLでリクエスト
  $curl = curl_init();
  curl_setopt( $curl , CURLOPT_URL , $request_url );
  curl_setopt( $curl , CURLOPT_HEADER, 1 );
  curl_setopt( $curl , CURLOPT_SSL_VERIFYPEER , false );
  curl_setopt( $curl , CURLOPT_RETURNTRANSFER , true );
  curl_setopt( $curl , CURLOPT_TIMEOUT , 5 );
  $res1 = curl_exec( $curl );
  $res2 = curl_getinfo( $curl );
  curl_close( $curl );

  // 設定E //
  // 取得したJSONデータをオブジェクト形式に変換する (ヘッダーサイズでTrimしないとFourSquareはエラーになる)
  $json = substr( $res1, $res2['header_size'] );
  $obj = json_decode( $json );

  // 条件分岐F //
  // エラー判定 (metaのcodeの値が200だと正常に取得されている)
  if( !$obj || !isset($obj->meta->code) || $obj->meta->code != 200 ) {
    //ログ出力して調査!
  } else {
    // 説明
    $data = array();
    $count = 0;
    // 条件分岐G //
    //取得したデータオブジェクトの -> response -> checkins -> itemsの中をループする //
    foreach( $obj->response->checkins->items as $item ) {
      // 処理H //
      //$itemの中にはチェックインの場所についての栄養が豊富に含まれています。緯度経度やコメントや市長など。詳しくはhttps://developer.foursquare.com/docs/api/users/checkins //
      // チェックインID
      $id = $item->id ;
      // ベニューのID
      $venue_id = $item->venue->id ;
      // ベニューの国 (国名は日本語で取得できる)
      $venue_country = $item->venue->location->country;
      // ベニューの都道府県 (取得した後、Google Chartの都合で末尾の「都」「府」「県」は除去する)
      $venue_prefecture = $item->venue->location->state;
      switch ($venue_prefecture){
        case '北海道':
          break;
        default:
          $venue_prefecture = mb_substr($venue_prefecture,0,-1, "UTF-8") ;
          break;
      }
      // ベニューの名前 (改行は除去しておく)
      $venue_name = str_replace(array("\r\n", "\r", "\n"), '', $item->venue->name);
      // チェックイン日時(オフセットと合わせる。要は日本の時間に合わせる)
      $createdAt = $item->createdAt + 54000 - 86400;
      // 日時の整形 (kintoneにあった日付データ形式に変更する)
      $createdAt = date( 'Y-m-d' , $createdAt )."T".date( 'H:i:s' , $createdAt ) ;
      // コメント (改行は除去しておく)
      $shout = ( isset($item->shout) ) ? str_replace(array("\r\n", "\r", "\n"), '', $item->shout) : '';
      // kintoneに投げるデータです。フィールドコードとデータ型を合わせることを忘れずに。
      // 処理I //
      //kintoneに投げるデータをここで指定します。文字列データは文字列型にキャストしておくとふっくら仕上がります //
      $data[$count] = array(
        "場所" => array("value" => (string)$venue_name),
        "コメント" => array("value" => (string)$shout),
        "国" => array("value" => (string)$venue_country),
        "都道府県" => array("value" => (string)$venue_prefecture),
        "日時" => array("value" => $createdAt)
      );
      $count++;
    }
  }

  // 処理J //
  // kintoneの対象のアプリIDを指定する。(1111というのはダミーです)。FourSquareからのデータもあわせて。
  $postdata = array("app" => 1111,records=>$data);
  // 処理K //
  // kintoneのアクセストークンを指定する。ベーシック認証がある場合はそちらもあわせて指定。
  $headers = array(
    "X-Cybozu-API-Token:" . "kintoneNOAPURISETTEIDESHUTOKUDEKIRUTOKUN",
    "Authorization:" . "Basic " . base64_encode("YUUZAAID:PASUWAADO"),
    "Content-Type:" . "application/json"
  );
  // 以下の******はお使いのkintoneのサブドメインを入れてください。
  $curl = curl_init('https://******.cybozu.com/k/v1/records.json');
  curl_setopt($curl, CURLOPT_POST, true);
  curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
  curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($postdata,JSON_UNESCAPED_UNICODE));
  curl_setopt($curl, CURLOPT_HEADER, true);
  // curlで出たエラーを補足するためのものです。
  $fp = fopen('curl.log', 'a');
  // 詳細な情報を出力する
  curl_setopt($curl, CURLOPT_VERBOSE, true);
  // STDERR の代わりにエラーを出力するファイルポインタ
  curl_setopt($curl, CURLOPT_STDERR, $fp);
  if(curl_exec($curl) === false){
    //ログ出力して調査!
  } else {
    return true;
  }
  curl_close($curl);

このスクリプトもウェブサーバーに送ってください。ファイルの実行権限付きで。あと、ファイルのUrlはきちんとメモしておいてくださいね(^_^)

次にcronの設定を行います。cron実行のための構文はサーバーによって違います。ここに載せているcron設定はさくらインターネットの例ですが、phpファイルの実行パスと、スクリプトのファイル名はどのサーバーでも求められるはずです。このレシピは毎日00:15に自動で実行するように設定しています。

Cron設定1

cron2-1

8.kintone上でデータの盛り付け

  目次へ↑

さて、7までのレシピ(2018/12時点の)に忠実に行うとチェックインデータは毎日順調にkintoneに流れ込むはず。あとはデータを盛り付けるだけ。「ライフログのkintone盛り alasql仕込みのGoogle Chart添え」とうたっている以上、最後の仕上げにGoogle Chartを添えるのを忘れるなかれ。それぞれの一覧ごとに違うマップを表示するJavaScriptを以下に示します。この中でkintoneのデータをalasqlで集計し、その結果をGoogle Chartで地図に表示しています。

世界地図の場合、処理A ⇒ 条件分岐B ⇒ 処理C ⇒ 処理D ⇒ 処理E ⇒ 処理V ⇒ 処理F ⇒ 処理W ⇒ 処理G ⇒ 処理H ⇒ 処理I ⇒ 処理J ⇒ 処理Kの順に進みます。
日本地図の場合、処理A ⇒ 条件分岐L ⇒ 処理M ⇒ 処理N ⇒ 処理O ⇒ 処理V ⇒ 処理P ⇒ 処理W ⇒ 処理Q ⇒ 処理R ⇒ 処理S ⇒ 処理T ⇒ 処理Uの順に進みます。

(function () {
  "use strict";
  // 処理V //
  // 再帰処理による一回のリクエスト制限を超えた全レコードを取得 //
  function fetchRecords(appId, opt_query, opt_fields, opt_offset, opt_limit, opt_records) {
    var query = opt_query || '';
    var offset = opt_offset || 0;
    var limit = opt_limit || 500;
    var allRecords = opt_records || [];
    var params = {app: appId, query: query + ' limit ' + limit + ' offset ' + offset };
    if (opt_fields) params.fields = opt_fields;
    return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params).then(function(resp) {
      allRecords = allRecords.concat(resp.records);
      if (resp.records.length === limit) {
        return fetchRecords(appId, query, opt_fields, offset + limit, limit, allRecords);
      }
      return allRecords;
    });
  }

  // 処理W //
  // json形式で取得したkintoneのレコードをalasqlで扱えるようレコードセット形式に変換 //
  function convertToRows(records) {
    var rows = records.map(function(record){
      var keys = Object.keys(record);
      var row = {};
      keys.map(function(key){
        row[key] = record[key].type === 'NUMBER' ? Number(record[key].value) : record[key].value;
      });
      return row;
    });
    return rows;
  }

  // 一覧ページ
  kintone.events.on('app.record.index.show', function(event) {
    // 処理A //
    //Google Cloud Platformで発行したAPI KEYです。
    //API KEYがない場合、以下の処理Cでgoogle.charts.load('upcoming', {'packages':['geochart']});としても動きますが、世界地図の色塗りができません。また、コンソールでエラーが表示されます。
    //作成したプロジェクトの認証はリファラーを限定するとよいです。https://subdomain.cybozu.com/* のように指定すると、対象のサブドメインに対して動作します。https://akvabit.cybozu.com/だと動きませんので注意が必要です。
    //あと、許可するAPIですが、「Maps JavaScript API」「Geolocation API」「Geocoding API」の三つを有効にしています。
    const ApiKey = 'k54u6 jkrawyeie-wkjykethiudarwhyeiu_rekyjur';
    // 条件分岐B //
    //世界地図ビューの場合 //
    if (event.viewId === 5351051) {
      // 処理C //
      //google chartを読み込む。種類は地図 //
      google.charts.load('upcoming', {'packages':['geochart'],'mapsApiKey':ApiKey});
      // 処理D //
      //google chartの読み込みが完了したらコールバックでdrawWorldMapを呼び出す //
      google.charts.setOnLoadCallback(drawWorldMap);
      function drawWorldMap() {
        var obj = {};
        // 処理E //
        //このファイルの3-17行のfetchRecords関数を呼び出す。対象アプリは自分で、フィールドは[場所][国] //
        fetchRecords(event.appId, '', ['場所', '国']).then(function(records) {
          // 処理F //
          //取得したレコードをSqlで扱えるようなレコードセットの形式に変換するconvertToRows関数(このファイルの20-30行)をご参照 //
          obj.rs1 = convertToRows(records);
          // 処理G //
          //alasqlでsql文のソースを組み上げ、バインドパラメータに処理Fで得たレコードセットをセットする //
          var result = 
            alasql(
              "SELECT t.[国], COUNT(t.[場所]) as [回数] \
              FROM ? AS t \
              GROUP BY t.[国] \
              ORDER BY t.[国]", [obj.rs1]);
          // 処理H //
          //google chartのデータテーブルのインスタンスを新たに確保する //
          var data = new google.visualization.DataTable();
          // 処理I //
          //google chartのデータテーブルの列と行を追加します。alasqlでグループ集計された国ごとの訪問回数です。 //
          data.addColumn('string', '国');
          data.addColumn('number', '訪問場所数');
          result.forEach(value => {
            data.addRow([value["国"], value["回数"]]);
          });
          // 処理J //
          //google chartの世界地図の色塗りの書式を設定する //
          var options = {
            datalessRegionColor: '#ffffff',
            colorAxis:{
                maxValue:500,
                colors:['#D8F6CE','#21610B']
            }
          };
          // 処理K //
          //google chartの世界地図のデータに処理Iで格納した内容を代入し、kintoneのヘッダスペースに描画する //
          var chart = new google.visualization.GeoChart(kintone.app.getHeaderSpaceElement());
          chart.draw(data, options);
        });
      }
    // 条件分岐L //
    //日本地図ビューの場合 //
    } else if (event.viewId === 5351053) {
      // 処理M //
      //google chartを読み込む。種類は地図 //
      google.charts.load('upcoming', {'packages':['geochart'],'mapsApiKey':ApiKey});
      // 処理N //
      //google chartの読み込みが完了したらコールバックでdrawJapanMapを呼び出す //
      google.charts.setOnLoadCallback(drawJapanMap);
      function drawJapanMap() {
        var obj = {};
        // 処理O //
        //このファイルの3-17行のfetchRecords関数を呼び出す。対象アプリは自分で、フィールドは[場所][都道府県] //
        fetchRecords(event.appId, '', ['場所', '都道府県']).then(function(records) {
          // 処理P //
          //取得したレコードをSqlで扱えるようなレコードセットの形式に変換するconvertToRows関数(このファイルの20-30行)をご参照 //
          obj.rs1 = convertToRows(records);
          // 処理Q //
          //alasqlでsql文のソースを組み上げ、バインドパラメータに処理Pで得たレコードセットをセットする //
          var result = 
            alasql(
              "SELECT a.[都道府県], COUNT(a.[場所]) as [回数] \
              FROM ? AS a \
              GROUP BY a.[都道府県] \
              ORDER BY a.[都道府県]", [obj.rs1]);
          // 処理R //
          //google chartのデータテーブルのインスタンスを新たに確保する //
          var data = new google.visualization.DataTable();
          // 処理S //
          //google chartのデータテーブルの列と行を追加します。alasqlでグループ集計された都道府県ごとの訪問回数です。 //
          data.addColumn('string', '都道府県');
          data.addColumn('number', '訪問場所数');
          result.forEach(value => {
            data.addRow([value["都道府県"], value["回数"]]);
          });
          // 処理T //
          //google chartの地図を日本地図の都道府県とし、色塗りの書式を設定する //
          var options = {
            region: 'JP',
            resolution: 'provinces',
            datalessRegionColor: '#ffffff',
            colorAxis:{
                maxValue:600,
                colors:['#F2FBEF','#21610B']
            }
          };
          // 処理U //
          //google chartの世界地図のデータに処理Sで格納した内容を代入し、kintoneのヘッダスペースに描画する //
          var chart = new google.visualization.GeoChart(kintone.app.getHeaderSpaceElement());
          chart.draw(data, options);
        });
      }
    }
  });
})();

いかがでしょうか? ここはカスタマイズビューでリッチにデータを表現しても良いですし、条件に応じて絞り込めば、さらに面白いこともできます。チェックインデータにはカテゴリーもありますので、例えば訪れた酒場だけを抜き出し都道府県で図示したり、訪れたラーメン屋だけを都道府県で抜き出すことだってできます。例えば私の趣味ですが、今までに訪れた滝を色分けすればこうなります。城とすればこう。駅だとこう。他にも登った山や訪れた蒸留所や酒蔵、ブルワリー。各地の日本酒や世界の酒、料理などで色分けしても面白そうです。
残念なことにGoogle chartはまだ市町村には対応していません。もしそうなればもっと面白いライフログが作れそうです。もちろん、地図で塗り分けるほかにもGoogle chartはかなりのグラフの種類を用意しています。そこは皆様の自由です。
なお、ライフログとは、別に人様に見せて放浪癖を誇るものでも、旅行経験を自慢するものでもありません。あくまでもプライベートな利用がよろしいかと思います。ご自身の今までの人生とこれから残された人生に何を成すかを定める助けになればそれで十分です。そのあたり、ライフログについての私の考えは、このレシピ同時に書いたこちらで世に問うてみました。またご覧頂ければ幸いです。
このレシピをまとめるなら、要するにkintoneをプライベート用途に使おうよ、との主旨です。そういう使い方がもっと増えればkintoneはより身近なものになるのですから!

9.当レシピの参考にさせていただいたブログ

  目次へ↑

最後になりましたが、このレシピを作るのに、以下の5サイトを参考にさせていただきました。ありがとうございました。

 Foursquare(Swarm) APIの使い方まとめ (サンプルコード付き)
 Get Check-Ins for a User
 kintone でSQLを使う
 GoogleのGeochartを使ってみた
Google Maps Platform


kintoneのPromiseを説明できるスキル


以前よりお付き合いさせていただいている株式会社アディエム様(https://adiem.jp/)より、
先日ご依頼を受けたたkintone開発案件は、何重にも入れ子になった多重Promise処理が必要でした。

弊社にてコーディングと単体テストを行い、無事納品にこぎつけられたのですが、
アディエム社の技術者様にもコードの説明を行う必要が生じました。

弊社の代表もPromiseの習得にはかなり手を焼いたのですが、そのスキルを習得できたかの判断基準は、その内容を人に説明できるかどうかです。つまり今回、うまく説明できたかどうかは、弊社代表がPromiseを理解できているかのベンチマークにもなりました。

説明を行った結果、アディエム社の技術者様にPromise処理をご理解していただけたようです。追加の処理を実装し、さらにテストまでも行えるまでになったとか。その結果を以下のようなメッセージでいただきましたのでご紹介します。

先日はコードのレクチャーをありがとうござました。
本日、検索部分のエラーハンドリングを追加し、本番環境にリリース致しました。
負荷テストとして4001件のデータを使用して、正常に更新されることも確認しました。

kintone APIのノーマル呼び出しパターン、kintone promise を使ったパターン、promiseでも thenにresolve, rejectを引き渡すパターン、thenとcatchを書くパターンと、かなりケースの整理が できました。また返値の扱いもデバックすることで理解が進みました。
まだうまく関数化して可読性の良いソースを書く自信はありませんが、長井さんソースを参考にさせて 頂きたいと思います。

以上、ご報告まで。

弊社代表は以前より、kintoneのエバンジェリストとしてサイボウズ社より任命されております。
最近はこうしたマンツーマンに近い形で、技術をお伝えする案件も増えつつあります。
その中でこうしたご評価を頂戴したことは、弊社代表にとっても自信になりました。もちろん、スキルの習得に終わりはありません。新たな技術も次々と世の中に生まれています。まだまだ切磋琢磨していかねば。精進します。

今後もアディエム社とはkintone案件のご提案からコーディング・テストまでを協業できる関係を築いていければと思います。
kintoneでのシステム開発のご相談、アディエム社、および弊社にお気軽にお寄せ下さいませ。

また、もし御社の技術者に対し、こうしたマンツーマン形式でのレクチャーをご要望の際は、
ご連絡をください! ご相談に乗らせていただきます。


box for kintoneでERR_CONNECTION_REFUSEDが出てしまった場合


kintoneをカスタマイズする際は、デバッグが欠かせません。
ブラウザーのコンソールに出るエラーメッセージはデバッグを行う上で重要なメッセージですよね。

とくに複数のカスタマイズやプラグインを入れたアプリでは、どのエラーがどのJavaScriptによって出力されるのか把握しておかねばなりません。そうしないと私のように脳のフラッシュメモリー容量が少ない人はすぐにCPU100%に達してしまうのです。

今回、弊社で別のプラグインを作るにあたり、box for kintoneで謎のエラーが出ていました。
エラーが出ているのにbox for kintoneのプラグイン自体はきちんと動作しているという。おやまあ。
謎です。全くの謎です。

と、そんな混乱は、以下にご紹介する方法で解消されました。
ウェブ上でもあまりこの件についての記事がなかったので、ニッチですが皆様のご参考になればとアップしました。

同様にファイアウォール系拡張機能とオンラインストレージの組み合わせだと出るかも。

0.box for kintoneとは

 オンラインストレージとkintoneを連動するプラグインです。こちらは「box for kintone」で検索していただければいくつも見つかると思います。使用上の注意をよく読み、用法 用量を守って正しくお使い下さい。
 こちらとか

1.謎のエラー

 ところが、一見すると想定通りに動いているように見えるbox for kintoneですが、コンソールには
 謎のエラーがうごめいています。

 な、なんやこれ?
 上図では見えませんが、エラーに「その心は?」と聞いてみると「ERR_CONNECTION_REFUSED!」と叫んでいます

 他のカスタマイズを進める都合上、こういうエラーは先につぶしておかないと。

 普通、ERR_CONNECTION_REFUSEDはウェブサイトにアクセスできないときに出るエラー。
 なのにkintoneの画面上にあるboxの窓には接続できていて、ドラッグするとファイルのアップロードもできる。謎です。

 何かが起これば、ファイアウォールを疑う。これは某氏(誰?)の金言です。

 と、そこで、ブラウザー(Google Chrome)に拡張機能として入れているマカフィーの存在に思い至ります。

2.拡張機能を探す。

 拡張機能については、下図のようにたどれば設定画面にたどりつけるはずです。
 

 ここでマカフィーウェブアドバイザーなる拡張機能が登場。これかな。
 

3.拡張機能の「ファイルのURLへのアクセスを許可する」機能をオフにする。

 詳細を押すと、こんな画面が。うむ。OFFになっている模様。これをOnにするといかがかな?
 


 
 こんな風にぽちっと。青くなりましたね。

4.念のためキャッシュやクッキーも消しておきましょうね。

 やってみたらうまくいかないので、ブラウザーがクッキーの消化不良を起こしているに違いない。ここは胃を空っぽにしてあげましょう。

 ここでは閲覧履歴データの消去を選びます。

 ここで閲覧履歴にチェックをいれて消去してしまうと、いろんなサイトで再アクセスが必要になるのでご用心ください。

 すると、謎のboxエラーはいなくなりました。めでたしめでたし。

5.終わりに

これは、2018/9/5時点の情報です。
もちろん、折角ファイアウォールさんがファイルへのURLを許可しないようにしてくれているのに、それを許可するということは、セキュリティ上リスクも増します。
そのあたりをご理解の上、参考にしていただければ。もちろん何かあっても弊社では責任はとれませんので。

なお、弊社はこれから、box for kintoneを拡張し、サブテーブルの行ごとにBOX上でサブフォルダーも作れるようにする予定です。


EBISU Tech Night Vol.2で登壇してきました


先日、恵比寿ガーデンプレイスで開かれた「EBISU Tech Night Vol.2」で登壇してきました。Vol.1に参加したその場でVol.2への登壇依頼をいただいたのです。

ご依頼主は「EBISU Tech Night」を主催されているシステム・インテグレーター様。大規模案件も多く請け負われ、若手の優秀な技術者を多く擁しています。そのオープンな社風と技術社さん達の感度の高さは「EBISU Tech Night Vol.1」に参加してビシビシと感じました。

事前にお題を伺ったところ、複数頂きました。その中から二つを選び、題材にしました。
 ・kintoneエバンジェリストとして小規模のお客様にkintoneを円滑に導入する秘訣
 ・kintoneをIoT案件に導入する勘所

確かにエンタープライズ案件でのやり方と、私がkintoneを提案し導入するお客様に対するやり方では方法論が違います。
また、IoT案件での導入は、今年の2月にQiitaに投稿したブログの内容が今も有効です。これらについて私の経験を語ることでお客様のお役に立てそうです。

「EBISU Tech Night」はお酒も入りながらのフランクな場です。なので私も途中から少し話す内容をやわらかくし、笑いを取ることにも気を配りました。

結果として、今まで私が登壇した中では一番笑いをいただけたように思います。また、話している間、スライドの内容を写真で撮ってくださる技術者さんが何人もいらっしゃいました。話の途中でkintoneを使ったことがある人が何人いるかを聞いたところ、7-8割ほどの方に挙手して頂きました。それもあって、私のお話は好意的に受け止めて頂けたのではないかと思います。実際、アンケートの結果でも好意的な結果が多く、励みになりました。また、終わった後の懇談でもお誉めの言葉を何人かの方から頂き、翌日もその会社様の中で私のスライドを参加できなかった方に共有いただくシーンが見られたとのことです。これはとても嬉しいことです。

こちらがスライドのURLです。
https://slides.com/yoshikazunagai/ebisutechnightvol2

私としては、意識して笑いを取ることを試み、それが功を奏したのは評価したいと思います。とはいえ、まだ自分の話術には向上すべき点が多いと思っています。今回は50-60名ほどの方の前で話しましたが、ゆくゆくは千人以上の方の前でも話せるだけの度胸をつけたいと思っています。

また、「EBISU Tech Night」での登壇依頼を数名の方からメールで頂けました。次回も前向きに登壇を考え、引き続き話すスキルを上げていこうと思います。併せてkintone Caféも開催し、エバンジェリストとして草の根で活動していきたいと思いました。


チーム応援ライセンス開始記念セミナーに登壇しました


5月16日にサイボウズ社27階で開かれた
「チーム応援ライセンス開始記念セミナー」(https://npo.cybozu.co.jp/blog/post/53/)
に弊社代表が登壇者として参加させて頂きました。

今回は、どちらかといえば自治会の元総務部長としての肩書きが主です。

直前に開かれた自治会の役員会で自治会名の実名を出すことが否決されました。
なので、実際の活用事例を写真や具体例で示しませんでした。
そのかわり、サイボウズLiveをサイボウズOfficeに切り替えるにあたり、
どういういきさつで選考を行い、どういう基準で採用したかの観点でお話ししました。
また、予算の限られた任意団体において、どれだけチーム応援ライセンスが重要かということも。

もちろん、最後にはkintoneエバンジェリストとして、
kintoneが自治会や他の組織にこれだけ効きますよ、ということも交え。
スライドはこちら(https://slides.com/yoshikazunagai/for_residents_association)

私は話し終わった後には、隣に座っていた方からお褒めの言葉も頂き、
懇親会でも様々な方からご挨拶や好意的な反応も戴きました。
サイボウズOfficeとkintoneのどちらを採用すればよいか、
との質問も数名の方から頂きました。
また、参加されていた当の自治会の方からも好意的なコメントを頂きました。

逆に反省点もあります。

・スライドに一切具体例を出せず、聞き手の皆様に抽象的な印象を与えたかもしれないこと。
・話の途中で質問してみたところ、自治会の役員経験者が聞き手の1割もいらっしゃらず、
 自治会を経験していない方には、私の話の意図が伝わらなかった可能性があること。
・質問の時間を取れなかったこと。
・kintoneを勧めておきながら、アプリのデモの時間をとらなかったこと。

実際、サイボウズ社より聞き手の皆様から頂いたアンケート結果(個人情報は省いたもの)を見せて頂きましたが、
反応がわかれています。
他の講演者より私の講演に高評価をつけて下さったかたもいれば、逆の評価をした方もいました。
サイボウズLiveの代替をOfficeにする決意がついたと仰って下さった方もいましたし、
懇親会でもそのような評価も頂きました。

そこも含め、どうすればうまく伝えられるか、私の方でも自治会や町内会、任意団体の方々に提案を行う上で、
とてもよい勉強になりました。

最後になりますが、ご出席くださったかた、サイボウズ社のみなさま、ありがとうございました。


サイボウズのクラウドサービスが2018年6月10日より一部利用できなくなる件の対応


このような案内をサイボウズ社から頂きました。

さて、本稿はこのような案内をサイボウズ社からいただいたけれど、どうすればよいかお困りの方へ向けて書いています。

実は弊社にも数カ月前、同様の内容が書かれたメールがサイボウズ社から届いていました。
その時は私はまだ先のことと思い、後回しにしました。
ところが今回、このような内容の案内を郵送で受け取るに至り、さすがにまずいと思って調べてみました。

この案内の中で対処法として書かれている通り、ウェブブラウザーやOSが古い場合は簡単です。
サイボウズのサービスを、新しいパソコンでしか使わないようにすればよいのです。

ところが問題は、ウェブブラウザーやOSが最新版なのにこのような案内が来る場合です。
それはリンクに記載されているチェックツールで問題なしと出る場合です。
問題なしと表示されるのに、なぜこのような案内が来るのか不安になりませんか。
もしかしたら、どうすればよいのか分からない方がいらっしゃるのではないでしょうか。
本稿はそういう方のために書かせていただきます。

実は私は最初にメールを受け取った時点でほぼ見当はついていました。
それはウェブブラウザー以外の場所から、サイボウズのクラウドサービスに接続している場合です。
ウェブブラウザー以外の場所とは、たとえばExcel、Word、Access、PowerPointなどのソフトウエアのことです。

弊社の場合、Excelからkintoneへデータを参照しているプログラムが問題でした。

    Set objweb = CreateObject("MSXML2.ServerXMLHTTP.6.0")
    If Err.Number = 0 Then
        Set CreateHttpObject = objweb
        Exit Function
    Else
        MsgBox "MSXML2.ServerXMLHTTP.6.0エラー"
    End If

    Err.Clear
        
    Set objweb = CreateObject("MSXML2.ServerXMLHTTP")
    If Err.Number = 0 Then
        Set CreateHttpObject = objweb
        Exit Function
    Else
        MsgBox "MSXML2.ServerXMLHTTPエラー"
    End If

上のプログラムは、Excelのマクロからkintoneに接続するライブラリの設定箇所を抜粋しています。
上で設定したライブラリを、以下のコードでkintoneに接続していました。

    Set objweb = CreateHttpObject()
    
    If objweb Is Nothing Then
        GetData = ""
        Exit Function
    End If
    
    Call objweb.Open("GET", "kintoneのURL", , "basic認証ID", "basic認証PW")

ここで最初に挙げた、

    Set objweb = CreateObject("MSXML2.ServerXMLHTTP.6.0")

または

    Set objweb = CreateObject("MSXML2.ServerXMLHTTP")

が問題です。

要するにこの二つのライブラリが古いのです。古いライブラリですがWindows 10でもサポートされているため、
エラーは特に起きません。

ところがこのライブラリは、kintoneへの接続の際にTLS 1.0という通信プロトコルを使うのです。
これがサイボウズ社のクラウド基盤側で検知されたため、メールや郵送でご案内を出されたのでしょう。
いつもながら、早め早めを見越したサイボウズ社の対応には感謝です。

弊社の場合、上記の処理を2社様にのみ使っていました。
とくに最初のお客様への導入が2012年の春でした。
それはkintoneがリリースされる前、つまりプレリリースの段階でした。
その時点でMSXML2.ServerXMLHTTP.6.0を採用したことが誤りでした。

それを受けて、

    Set objweb = CreateObject("WinHTTP.WinHTTPRequest.5.1")
    If Err.Number = 0 Then
        Set CreateHttpObject = objweb
        Exit Function
    Else
        MsgBox "WinHTTP.WinHTTPRequest.5.1エラー"
    End If

と改修しました。WinHTTP.WinHTTPRequest.5.1であればTLS1.0は使いません。
3月の初めに改修を行いましたが、今のところエラーもなく動いています。
(サイボウズ社側でまだTLS1.0として検知されていたら問題ですが)

なお、

    Set objweb = CreateObject("MSXML2.ServerXMLHTTP.6.0")

を使っている場合は、

    Call objweb.Open("GET", "kintoneのURL", , "basic認証ID", "basic認証PW")

でエラーなく通ります。
ですが、

    Set objweb = CreateObject("WinHTTP.WinHTTPRequest.5.1")

に変えた場合、

    Call objweb.Open("GET", "kintoneのURL", , "basic認証ID", "basic認証PW")

ではエラーが発生します。

    objweb.Open "GET", "kintoneのURL", False

と変えるとよいでしょう。
ちなみにベーシック認証の指定は、

    Call objweb.setrequestheader("Authorization", "Basic " & encode64("basic認証ID:basic認証PW"))

とopenの後のコードで行っているので、その変更はありません。

というわけで、同様の案内をサイボウズ社からいただいていて、ウェブブラウザーやOSのチェックでは問題ないと表示される方、
は本稿を参考にしてもらえれば幸いです。

もし必要であれば弊社宛にご連絡を頂戴できれば、ご相談にものらせていただきますよ。


今年はエバンジェリスト活動を活発に


年始の抱負にも書きましたが、

今年はkintoneエバンジェリスト活動に力を入れます。

 

まずは、今年度もkintoneエバンジェリストとして無事に更新頂きました。

こちらはバレンタインデーに合わせてサイボウズさんよりいただいたプレゼントです。
ハッピーエバレンタイン♫ ありがとうございます!
ハッピーエバレンタイン

昨年はほとんどエバンジェリスト活動が出来ませんでした。今年はその反省を踏まえなければ。

サイボウズさんからも昨年の弊社代表のエバンジェリスト活動の低調さには、苦言をいただきました。

少なくとも一昨年並みの活動レベルに戻し、その上に行くことが求められます。

 

その反省から、今年はkintone案件を複数提案し、準備に動いています。その中にはkintoneを使った新規サービスも含んでいます。

先日は久し振りにプラグイン開発も行いました。お客様専用のプラグインなので、公開の予定はありませんが。

また、来週にはkintoneに関するインタビューも受ける予定です。

 

去年の反省を踏まえた上で、弊社代表が何を武器とするか。技術力がある程度あるのは前提で、そこに何を加えられるか。何を武器としてエバンジェリストとしての存在感を出して行くか。弊社代表が至った結論。それは書く力です。文章力。そこに活路を見出そうと思います。そして、去年に比べて一味違った活動を展開できればと思っています。


Save The Live!~サイボウズLiveを救え~


1.読まなくてもいい前書き

2017年の秋晴れを引き裂くように、そのお知らせは降ってきました。そう、サイボウズLive終了のお知らせです。
その直後、お客様から早速ご連絡が。「Liveが終わる。代替案を検討してほしい」と。「はいっ!承知でございます」。

直後に開催されたCybozu Days 2017の基調講演でサイボウズ青野社長からLive終了の事情説明やお詫びをいただき、ようやく事態の深刻さを悟った私。Daysで仕入れた情報では、移行ツールが提供されるとかされないとか。でもそれはあくまでも未確定情報。リサーチして弊社でも移行できるようにしておかないと。

そう思っていたところにkintone Advent Calendarの枠が残り少ないというご連絡を頂きまして。最後の一枠に滑り込めました。Advent Calendarへの参加は3度目なのですが、今回はサイボウズLiveをからめればネタとして旬ではなかろうか。そんなことを、サイボウズのUっ氏~にお伺いを立てたところ、歓迎いただけまして。では取り掛かってみよう、と考え始めたとたん、Advent Calendarの4日目にアップされたのが「kintoneサイボウズLive化作戦」。正直やられた、と思いましたね。けど松田さんの記事を拝見するとkintoneへのデータ移行までは触れていない様子。これは私がやるしかないでしょう。まだ出番まで3週間はあるし、案件の合間でも何とかなるはず。2019年の4月までしか賞味期限のない記事ですが、どなたかのお役に立てれば。せめてもの長井サンタの贈り物です。

目次

1.読まなくてもいい前書き

2.サイボウズLiveのデベロッパー登録を行い、APIキーを入手する。

3.APIリファレンスを読む。理解する。

4.類似の参考サイトを探す。

5.グループと個人の違いを理解する。

6.取り込み方法のインターフェース検討と実装。

7.サイボウズLive APIの制限事項と、kintoneの対応機能の検討。

8.実装。

2.サイボウズLiveのデベロッパー登録を行い、APIキーを入手する。

実は今まで、サイボウズLiveのAPIは存在こそ知っていましたが、完全スルーしていました。
なので今回はデベロッパー登録から始めました。まずはここからやってみましょう。

 ・https://developer.cybozulive.com/apps/top にアクセスします。

・APIの対象を選びます。グループZを選ばないと全データが取得できないのでグループZを選びます。アプリケーションの種類はクライアントにします。

・Consumer KeyとConsumer Secretを入手します。大切に保管しておきましょう。

・上で入手したConsumer KeyとConsumer Secretを使います。

3.APIリファレンスを読む。理解する。

続いてAPIリファレンスです。これを読まねば話になりません。
 https://developer.cybozulive.com/doc/current/

なるほどサイボウズLiveは、認証にOauthを使っているのですね。
JavaScriptだけでの実装だとちょっと面倒かな。oauth.ioとか試してみればできそうな気もするけど後回しにします。
PHPだとOauthの実装したこともあるし多分いけるでしょう。
そんなわけで、まずPHPで実装し、時間があればJavaScriptでやってみよう、と決めました。

4.類似の参考サイトを探す。

ところが、サイボウズLiveってAPIがあまり活用されていなかったのか、情報があまりウェブ上にないのです。
そんなわけで私が公式のサイボウズLive APIドキュメントページ以外で
参考にさせて頂いたのは、まだプログラマーですが何か?さんのページのみです。ありがとうございました。

こちらの情報によると、pearで Net_URL2、HTTP_Request2、HTTP_OAuthを使うのが良さげな感じ。私もそれにのっかりました。
サーバーはさくらインターネットさんのスタンダードプランが手元にあったので使ってみようとおもいます。
と思ったら、ちょっとだけ手間取りました。以下は読み飛ばしてよいです。
 ・Oauthの実装に手間取りまして。
 ・そもそもpear自体がさくらインターネットのサーバーにインストールされていなかったり。
 ・なのでgo-pear.phpを使ってインストールしようとして。
 ・そしたら、ファイルだけでなくディレクトリのパーミッションも755にしないと動かない、とか。
 ・で、pearのインストールができたと思ったら、go-pearのインストーラーからNet_URL2パッケージを入れる際、なぜか古いバージョンしか入れられず。
 ・するとHTTP_Request2、HTTP_OAuthが要求するNet_URL2のバージョンを満たさずさあ困った。
 ・仕方ないのでSSH接続でコマンドからインストールする羽目になりました。
 ・しかも後ほどftpやsocketでもはまってしまい、仕方なくあとでphpのバージョンを7.1.11に上げました。これらの処理に影響はありませんでしたが。

・というわけで、本記事の前提となるバージョンです。
  kintone :::2017/11月アップデート版
  php:::7.1.11
  jQuery:::3.2.1

5.グループと個人の違いを理解する。

さて、Oauthのライブラリをインストールしたことで、サイボウズLiveへの接続はすんなり成功しました。
ところが、上記のサイトでご提供されている情報は認証の部分とXMLのダウンロードまでです。取得したサイボウズLiveのデータをkintoneへ登録する部分はこちらで実装せねばなりません。
実装としてはphpでXMLを解析し、要素や値をkintoneにアップすればよいはず。

ここでサイボウズLiveのデータ構造をどのようにkintoneに移行するか考えないと。
つまり、個人データとグループのデータをどう関連づけ、kintoneに持ってくるか、という問題です。
例えばスケジュールのデータ。グループごとに分かれています。でも、データ構造は同一のはず。
実は個人スケジュールのデータには各グループのスケジュールも全部含まれるので、それを持ってくればよいのですが、グループ単位のスケジュールで取り込めるようにしておかねばあとで困るでしょう。

つまり考えねばならないのは以下の通り。
kintoneで、グループごとにスケジュールアプリを分けてつくるのか、それとも一つのスケジュールアプリに複数のcybozu Liveグループのデータをマージさせるのか。
言い換えれば、kintoneのアプリ単位にユーザーの権限を制御させるのがよいか、それとも同じアプリの中のレコード単位で権限を制御させるのがよいか、ということになります。

考えた結果、kintoneのグループスケジュールアプリは一つにし、グループのデータを別々に登録するようにしました。
個人スケジュールはグループスケジュールと別々にしなければ、あとでデータが混在して困るので、別にしました。ToDoも同じく個人とグループを分けています。

6.取り込み方法のインターフェース検討と実装。

個別にダウンロードするにあたっては、インターフェースを考えたほうがよいですね。
kintoneアプリの一覧画面にダウンロードリンクを貼ることにしました。ヘッダー下のスペースにセレクトボックスを二つ配置し、それに応じてphpへのリンクを切り替えるようにしています。

 取り込み部分のインターフェースはこんな感じで

・初期状態はこんな感じ。

・左側のドロップダウンがグループです。サイボウズのアカウントが属するグループをリストアップし、先頭は個人データとしています。

これらのグループは私が実際に使っている/いたものです。kintoneやcybozu界隈のグループも昔cybozu Liveにあったのですよ。懐かしい\(^o^)/

・右側のドロップダウンが対象となるデータです。左側が個人データなので、5種類のデータを取り込み対象として出しています。
 (「全て」と「テーマチャット」と「ダイレクトチャット」と「コネクション」は本記事では実装していません。)

・左側のドロップダウンをグループにすると、右側のドロップダウンの内容もかわります。4種類のデータを取り込み対象として出しています。
 (「全て」と「メンバーリスト」は本記事では実装していません)

・両方のドロップダウンの選択の値に応じて、すぐ下のリンクの文字列とurlが切り替わります。

ちなみに、各kintoneアプリはこの部分に同じJavaScriptファイルを読ませることで共通化しています。
なお、リンクをクリックした際も、それぞれ定められたアプリにデータを放り込む仕様にしています。そのため、各アプリのリンクから違うアプリの取り込み処理が可能にしています。とはいうものの、混乱はないはずです。

 この部分の実装について説明します。まずはJavaScriptの部分を。

・まず、アプリに対してJavaScriptを登録します。上にcybozu CDNからjQueryを。下にこの後説明するgetGroupList.jsを登録します。

・getGroupList.jsの先頭から。

ここはkintoneではお約束の記述ですね。
4行目のjQuery.support.cors = true;は jQueryのAjaxで他のドメインの値をとるときの呪文です。昔作ったコードを持ってきたので、今ならkintone.proxyのほうが良いと思います。kintone.proxyについてはこちらを。
その下のescapeHtml関数は、サニタイズ用です。まあ今回の案件についてはそういう心配は無用なのですが。ちなみにこのJavaScriptファイルはjswatchdogでチェックすると、三か所XSS Warningが出てしまいます。optionの部品オブジェクトを代入する際に「ちょっと自分危ないんとちゃう!?」と怒られてしまいます。放置しています。ごめんなさい。

・13行もkintoneのJavascriptではおなじみです。一覧の表示時に呼び出されるイベントです。

14-16行は、複数のドロップダウンを出さないようにするためのステップです。すでにドロップダウンがあれば処理を終了します。
19行から下は、jQueryのAjaxのお約束です。上に書いたようにkintone.proxyのほうがよいかも。
ここで、https://dummydesse.sakura.ne.jp/kintonefromcybozulive/cbl_grouplist.php を呼んでいるわけですね。このphpについては下で解説しますので、少々おまちを。

・26行から下は、データがきちんと取れた際の処理です。ここでselect部品やリンク部品を生成しています。
27行がphpから取得した値をこちらで用意した変数に代入しています。

36-37行は、初期値としてリンクに表示させる個人データ「全て」のリンクを表示しています。

・ここではphpが貢いでくれたグループのデータをselect部品のoptionに代入しています。
datalistは処理対象となるデータの種類です。初期値が個人データなので、個人データに対応するデータを配列に登録しています。

・ここでは配列のデータ種別をselecgt部品のoptionに代入しています。

さらに kintone.app.getHeaderMenuSpaceElement メソッドを使い、上で生成したselect部品やリンク部品をkintone画面に表示しています。

・66行目以下は、データ種別のselect値が変わった際に、リンクの表示文字列とurlを変更する処理です。

・78行目以下は、グループのselect値が変わった際に、リンクの表示文字列とurlを変更する処理です。個人とグループではデータの種別も変わるため、リンクの文字列やurlもあわせて変動させねばなりません。いやはや、大変ですね。

・以下は失敗した場合の処理です。ここはエラーメッセージなので、解説は割愛させてください。要するに疲れてきました・・・

 続いてphpでcybozu Liveで認証する部分です。

・といっても、この部分はまだプログラマーですが何か?さんのページを参考させてもらい、それをかなりの部分で使わせて頂きました。改めて感謝です。
とはいえ、私の実装ではcybozu Liveの認証部分は別phpファイルに切り出すなどの改良を加えています。それが5行目ですね。cbl_oauth.phpにお任せしているのです。3,4行目は上に書いたようにさくらインターネットのスタンダードプランに入れたhttp_Request2とhttp_oauthを呼び出しています。あまりサーバーに詳しくない方にとっては、この二つのインストールは厄介かもしれません。 

続いて、認証情報を格納する変数を三つ用意します。この三つの変数は以下の処理でも使うため、すぐ下のgetAccessTokenへは参照渡しをして、getAccessTokenの中で代入された値を使えるようにします。
そして、getAccessTokenを呼び出した結果をaccess_token_infoに格納します。getAccessTokenの戻り値は配列なので悪しからず。
さらに、10行に書いているとおりヘッダーでおまじないをとなえます。これはphpの中で違うドメイン(cybozu Live)のデータをとってくるときに、このおまじないを忘れると、11行目に書かれているような反撃を食らうためです。いわば白魔法の防御呪文のような感じです。

・12行目のtryで処理を開始します。
13行目のHTTP_Request2は上で読み込んだRequest2ファイルの中のクラスをインスタンスとして使いまっせ、ということです。 
14行目はssl_verify_peerをFalseにしています。つまりSSL証明書の検証を要求しませーん、という意味です。
17行目のHTTP_OAuth_Consumer_Requestは上で読み込んだOauthファイルの中のクラスをインスタンスとして使いまっせ、ということです。
18行目は、HTTP_Request2の設定に基づいて一旦つながしてーや、という意味です。まあ斥候みたいなもんです。

20行目は斥候がまず様子見した接続を、本体が認証情報を掲げて大手を振って入場するための準備です。上でとってきたcybozu Liveの認証キーをHTTP_OAuth_Consumerに渡してあげるのです。通行許可証ですな。厳重に4種類の通行許可証を渡してあげるわけです。
21行目は、上で準備した通行許可証を、18行目で斥候が先に接続のお伺いを立てて問題ないよーという情報を御旗にたてて、cybozu Live城にアクセスします。
26行目は、個人フォルダのパスです。cybozu Liveの個人Idはメールアドレスなのです。
27行目はフル許可設定で個人フォルダのパスをなければ生成します。

・34行目はcybozu LiveのAPIのエンドポイントと定められたURLあてにリクエストを送ります。
arrayの中にパラメーターを含めればいろいろとリクエストができますが、ここではグループ一覧をとりたいだけなので、パラメーターはなしで。なお、apiの仕様はこちらに載っています。

36行目でリクエストの結果が $xml0 に代入され、エラーがなければ40行に処理がいきます。
40行は、ここでcybozu Live apiに進呈されたグループ情報の配列に個人を加えるための初期値です。先に説明したインターフェースでは最初に「個人データ」がありました。
45行目では先頭に「個人データ」が入った配列がグループデータに従って生成されます。
47行目では、グループidに従って並び替えます。その際「個人データ」キーは0を指定しているので、かならず先頭です。
48行目は、JavaScriptが読めるようなjson形式でデータを返してあげています。echoで返してあげるとJavaScriptはデータとして喜ぶのです。
49行目以下はエラー処理。疲れたので割愛させてください・・・

 続いてphpでcybozu Liveで認証するコア部分です。

・ここに各種認証キーを記載します。

$consumer_keyには、cybozu Live apiのデベロッパー登録してきたキーを書き込みます。
$consumer_secretには、cybozu Live apiのデベロッパー登録してきたシークレット値を書き込みます。
$xauth_access_token_urlには、cybozu Live apiがトークン取得用としてご用意しているurlを指定します。
$authusernameには、あなたのcybozu Liveのログインアカウントを書きます。
$authpasswordには、あなたのcybozu Liveのログインパスワードを書きます。

・続いて認証リクエストの部分です。先ほどの説明と重複する部分もあるので、21-28行までの処理は説明を割愛しますね。 

30行目では、OauthのsendRequestのメソッドに対して、urlや認証情報のつまったパラメータを渡してあげます。
するとレスポンスにアクセストークンが戻ってくるのですね。あとは、phpのparse_str関数で$access_token_infoにトークン文字列を代入してあげるだけ。簡潔です。

・エラーハンドリングの部分は、割愛でお願いします。

ちなみに今回はkintoneのアプリ一覧画面上に実装しましたが、本当ならばポータルからダウンロードさせるのが親切な気がします。
多分、同じように実装すればいけるはずです。
お時間のあるかた、おもちとみかん食べながら、いかがでしょうか?

7.サイボウズLive APIの制限事項と、kintoneの対応機能の検討。

ところが、こうやって移行の検討を深めてみると、いくつか問題が出てきます。

Q1.サイボウズLiveのユーザーをそっくりkintoneのユーザーとして移行できない。なぜなら開発者ライセンスは5ユーザーしか作れないから。無料だからとたくさんアカウントを作ったはよいが、kintoneアカウントは有料ですから。
     ↓
 A1.kintoneのユーザーやグループの利用はあきらめ、ドロップダウンか文字列(一行)にデータを入れるしかない。またはユーザー一覧のアプリを作ってルックアップや関連レコードで連携させるとか。今回の記事ではドロップダウンか文字列(一行)か複数選択でお茶を濁しています。

Q2.サイボウズLiveのAPIに用意されているファイルダウンロードがそもそも画像のみ。
     ↓
 A2.画像しかとりこまないようにする。
 (ところが、今回は画像のアップロードの実装はしていません。以下は言い訳です。
  ・さくらインターネットのphpがftpサポート外だということをうかつにも忘れていまして。
  ・sshでログインし、コマンドからphpのconfigureやmakeを行ってftpやsocketを有効化した。参考サイト参考サイト
  ・ところがそれにも関わらずダウンロードができない。調べてみたらさくらのftpサーバーがRETR未対応ということで、サーバー自体がphpのftpを頑として受け付けないということに気がつき万策尽きる。
  ・curlだとローカル経由でアップロードした場合はfilekeyの取得ができるが、リモートURL経由ではresponseがNullになってしまう。
  ・処理の一貫性の観点では、ファイルがローカルに自動的にダウンロードされる実装を加えないと、ダウンロードの時点で一度別処理を挟み、それ以降のkintoneアプリに添付ファイルを登録するためのfilekey取得をあらためて行う必要が・・・・

Q3.サイボウズLiveのコメントをkintoneに移したい。ところが、APIトークン利用でのコメント追加だとコメント追加者がAdministrator扱いになってしまう。コメントの記載を各ユーザーで書かせるには、毎回認証ユーザーを切り替えねばならず、実装が面倒。
     ↓
 A3.コメントについてはサブテーブルで実装しました。松田さんのご提供アプリでも一部サブテーブルでしたし。

Q4.マイカレンダーにグループスケジュールへのリンクを実装するのが面倒。
     ↓
 A4.アプリ間の連携を円滑にするには、リンクの設定が不可欠ですが、今回の記事では実装していません。いずれやります。

Q5.スケジュールの日付範囲の取得がサイボウズLiveのAPIの仕様だと制限がある。(FromからToが365日以内でなければならないとか)
     ↓
 A5.他にも、cybozu Liveのスケジュールには通常予定と期間予定、繰り返し予定があります。その実装がちょっとひと手間必要なので、今回の記事では実装していません。いずれやります。

Q6.cybozu LiveのAPIの仕様では、一回当たりのデータ取得件数が最大100件になっている。
     ↓
 A6.これは再帰処理などで、実装可能だと思います。が、今回の記事では実装していません。いずれやります。

8.実装。

あとは、個別のプログラムごとに移行をわけ、それぞれのアプリごとに取り込むだけです。
せっかくなのでアプリは松田さんのご提供いただいたテンプレートをベースに使わせて頂き、適宜修正するようにします。

・今回ここで呼び出すurlとは、右下のリンクのUrlのことです。
 

 まずはxmlを配列に読み込むためのphpの中身です。

・では個人スケジュールから始めてみましょう。cbl_schedule.phpというファイルです。
20行目までは、グループ一覧を取得する処理とほぼ同じなので、割愛します。

・続いて認証処理からの流れです。こちらも32行目まではグループ一覧の取得時の処理と同じなので、割愛しますね。

重要なのは33行目です。ここではcybozu Live apiの個人スケジュールのエンドポイントurlを呼び出しています。
個人スケジュール情報のapiの仕様はこちらに載っています。
ここで、array内に取得する情報の条件をパラメーターで渡します。パラメーターのうち、個人スケジュールについては必須なのはありません。が、期間を指定しないと、思ったデータがとれません。条件の指定方法については、上のapi仕様に載っています。
とくにterm-startとterm-endの設定が重要です。
またmax-resultsは100が基本でしょうね。
embed-commentもコメント情報を取得するためには必要なパラメータです。
cybozu Live apiの一回当たりの取得件数は max-resultの最大値が100なので100件が限度です。
そこで全データを取得するためには、start-indexの値を毎回変動させつつリクエストを投げる必要があります。
38-39行目の置換の意味ですが、cybozu Liveから帰ってくるxmlのデータに名前空間付のタグがあります。名前空間付きのタグとは、
 

<cbl:group valueString="長井家予定" id="2:65107" />
    <cbl:comments>
      <cbl:feedLink>
 

のようなコロン付きのタグですね。このタグの内容はchildren(“cbl”,true)->group といった方法でパースすればデータの取得は可能です。ただ、コメントタグの中で、各コメントごとにある <entry cbl:sequence=”2″> がうまく配列として読みないのです。なので仕方なくコロンを _ に置換する方法を採りました。
45行目の simplexml_load_string で、取得したXMLデータをオブジェクトとして扱えるようにします。
46行目は $authoremail に投稿者のemailを代入します。$list0->author->email が 下記のようなxmlのデータを取得する際の命令文です。この$list0がsimplexml_load_stringで取得してきた全体のオブジェクトにあたります。その中の author タグのさらに下にある emailを取得する際にこの命令を投げればいいのですね。
 

   <author>
     <name>長井 祥和</name>
     <uri>2:39920</uri>
     <email>kakakakaka@dummy.jp</email>
   </author>
 

48行目は 取得したデータをkintoneに放り込むためのphpを呼び出しています。

・続けて、エントリごとの中身を取得に行きます。

49行目の foreach($list0->entry as $entry0) { はエントリータグを上から順にループ(フェッチ)させる命令です。
50-51行目は、entry毎のidを取得します。cybozu Liveのデータはカンマ区切りで四つ目の値がエントリidなので、それを採りに行きます。
52行目から57行目まで、取得時に(string)という文字があります。これは強制的にオブジェクトの型を文字列として変換する処理となります。
55行目と56行目にある cbl_when->attributes()->startTime というのは、XMLでいうと

<cbl_when startTime="2011-04-06T14:55:00Z" />

 のようなstartTimeの属性の値を取得するための命令です。 
59行目はXMLでいうとcbl:facility というタグを取得する際、タグの有無を判別するための文です。
タグがあれば、その中をループします。

・続いてxmlの中身を取得していきます。このあたりのxmlの構成は、apiの仕様を確認するか、もしくは後で紹介するサーバーに保存したxmlファイルの中身を確認するとよいでしょう。

・81行目から下は、エントリ本文に添付ファイルがあった場合の処理です。
添付ファイルの有無は if ($entry0->cbl_attachment) { で判別可能です。

fileDownload の関数は以下に説明しますが、サーバーにxmlファイルを置くための関数です。
93-94行目にある get_filekey_from_kintoneはkintoneにファイルをアップロードし、その添付ファイルをレコードを紐づけるためのfilekeyを取得するための関数です。ただ、言い訳にも書いた通り、ローカルへのダウンロードがうまくいきませんでした。そのため、今回は関数の中身は恥ずかしくてお見せできません。ですが、94行目のようにローカルファイルを指定するとfilekeyの取得はできることは確認済みです。

・98行目以下は、コメントのデータ取得です。
104行目の foreach( $cbl_entry as $entry2 ){ は複数コメントを順繰りに参照するための命令です。
なお、cybozu Liveのapiが提供するXMLファイルのコメントの一つ一つには番号が振られています。それが105行目の $entry2->attributes()->cbl_sequence で取得できます。
つまり、106-110行目の処理は、コメント一つ一つの番号をキーに設定し、連想配列としてコメントのそれぞれの値を格納しているのです。

なお、132-133行目の処理も、実際はうまく動かないため、コメントアウトしています。

・ここからは上の方法のおさらいですね。

・152行目から下は、配列に、取得してきた各値を代入していく作業です。

なお、179行目でheaderを指定してます。これは、呼び出し元に戻す命令です。ここでいう呼び出し元とはkintoneの一覧画面のことですね。これでkintoneにデータを登録した後、もとの画面に何事もなかったかのように涼しい顔で戻れるのです。

・で、こちらはfileDownload関数です。
cybozu Live apiはそもそも画像ファイルしかダウンロードができず、excelやwordファイルだとエラーになってしまいます。
エラーを回避するために拡張子が画像のものしかダウンロードさせないようにしています。ここではサーバー内の場所にファイルを保存するようにしています。

 続いて取得した配列データをkintoneに投げ込むphpの中身です。

・この辺りはデータを登録するためのトークン周りの処理ですね。
念のためですが、トークンの発行時には権限にレコード追加も含めておいてくださいね。

・ここでは、コメントのデータをkintoneのサブテーブルのjsonデータに整形しています。

・ここではkintoneに投げるデータの整形部分です。上でサブテーブル用に整形したコメントのデータも登録しています。

・取り込んだデータをcurlでリクエストとしてkintoneに投げています。

・カレンダービューに取り込んだデータが表示されます。

・レコード詳細もこの通りです。
コメントの内容もサブテーブルに入っていますよね。

とまあ、今日はここまでにしておきましょう。こちらでは個人スケジュール、グループスケジュール、個人ToDo、グループToDo、グループ掲示板まではデータの取得スクリプトを作成済みです。
 実は一つ取り込みのコツがわかれば、あとはデータ形式の差だけなのです。返す返すも添付ファイルの登録ができなかったのは残念ですすが、おいおい実装してこちらのブログをアップデートする予定です。

もしcybozu Liveからデータ移行をしたいというお客様がいらっしゃいましたら、お手伝いできますのでご相談ください。お安くさせて頂きますよ。


Connect 2016 in Koriyamaに参加します


お知らせです。

11/12-13に福島県郡山市でConnect 2016 in Koriyama, with UDC (ハッカソン)が開催されます。
https://www.techno-media.net6.or.jp/newmedia/human/1371

そこに弊社も参加させて頂くことになりました。ただし、参加といいましても、皆さんと一緒にアーバンデータチャレンジ(UDC)2016の応募を目指してアプリを作る側ではありません。アプリを作るための技術セミナーの講師として参加させて頂きます。弊社代表が担当する技術セミナーはkintone。

当日は、福島に対する想いを沢山お持ちの皆様のアイデアが、アプリ開発を通じて実現されてゆくプロセスを目の当たりに出来るはずです。熱気溢れる場となることでしょう。奇想やビジネス感覚に溢れたアイデアにも出会えることでしょう。弊社代表もハッカソンには今まで誘われていながら、一度も参加できていませんでした。今回はとても刺激を受けることになるはずで、今から楽しみにしています。

残念ながら応募者多数により、新規受付は締め切られてしまったそうです。が、当日の模様については可能な限りブログにてレポートさせて頂きたいと思っています。乞うご期待!


kintone Café 福島 Vol.1 ではkintone愛を語りました


%e5%86%99%e7%9c%9f-2016-10-01-14-57-489/30の郡山商工会議所でのセミナーについては、こちらのブログで反省点も含めて報告させて頂きました。こちらは、その翌日10/1に行われたkintone Café 福島 Vol.1についてのエントリーとなります。

セミナーを終えてホテルに投宿した私は、反省と手応えの両方を握り締めていました。今日のセミナーで得た反応を明日のkintone Café 福島 Vol.1にどう活かすか。反省点を改善して臨むのか、それとも手応えをより伸ばして臨むのか。

私が選んだのは、手応えをより伸ばす方でした。もちろん、セミナーで得た反省点は反省点として真摯に受け止める必要があります。しかし、私は元々減点評価でなく加点評価で物事を捉えがち。それにkintone Café 福島 Vol.1を翌日に控え、今さら付け焼刃の修正もないだろうと。それよりも、今日のセミナーで得た手応えを元に、長所をより出していこうと判断しました。

もともと主催者の方に事前にお伝えしていた私の演目のタイトルは「開発者から見たkintone」。であれば、タイトル通り、開発者から見たkintoneの良さをとことん語ってやろうではないか。そう前向きに考えました。kintoneと出会って感じた衝撃や、そこから私が得た技術者としての手応え、管理部門として埋没し掛かっていた自分が再び技術者としての可能性に目覚めるまで。そんな自分のkintone愛を書きまくってやろうではないか、と。しかも、kintone Café 福島 Vol.1には、私をkintoneに誘い、その後エバンジェリストにまで推挙して下さった方がサイボウズ社からいらっしゃるとか。であれば、その方への御礼もしたいではないですか。そしてそういった私の思いを話すことで、福島や郡山の技術者の皆さんにkintoneの良さが伝われば、開発プラットホームとして選ぶに値するとに思って頂ければ本望ではないか。そう思いました。

それからは、月末締めの準備と並行しつつ、喋る内容を再度練り直す作業に没頭しました。そして、練り直すと同時にスライド原稿にも手を入れました。結果、書き上げた私の演目内容は、今までmixiでもTwitterでもFacebookでもブログでも書いたことのない、個人的な技術者としての想いが詰まったものになりました。

その夜は郡山のThe Bar Watanabeでお酒を心行くまで楽しみ、翌朝も月初の仕事をこなした後、気合を入れて往復40キロの自転車旅を敢行しました。おかげでkintone Café 福島 vol.1の会場であるCO-BA KORIYAMAに到着したのは開始ぎりぎりの15:00でしたが。

%e5%86%99%e7%9c%9f-2016-10-01-15-06-35会場となるCO-BA KORIYAMAはとても広く、しかもスツールやカウンターテーブルが置かれたり、ソファに絨毯のスペースがあったりと、とても居心地のよい空間となっていました。なぜか中央に鎮座する卓球台兼テーブルもポイント高いです。そして、人数が前日のセミナー並みに集っています。しかも、年齢層がぐっと下がり、いかにも技術者が集った感が醸し出されてまして。これは多分、セミナーに比べて技術者率がぐっと上がったということでしょう。期待度をひしひしと感じました。そして、サイクリングの疲れでフラフラでしたが、私が昨夜書き直したトーク内容が通ずる、という予感がしました。

私の話した内容はこんな感じです。

%e5%86%99%e7%9c%9f-2016-10-01-23-19-36初めてのサイボウズ社との出会いから、kintoneβテスターとしての申込み、ユーザー会でサイボウズ社の社風に触れたこと、kintone発売開始と私自身kintone案件受注。この辺りのエピソードを当時のTwitterの画面ハードコピーをお見せしながら紹介しました。次いで、私が常駐先の多忙により全くkintoneから離れてしまったこと。その間、技術者としてkintoneの進化に全く付いていけない焦り。技術者として袋小路に入ってしまったという危機感と絶望。最後に、エバンジェリストとしての推挙から法人化を経、ジェネラリストよりもエキスパートたれ、という言葉に開眼し、kintoneエキスパートを目指すことに社業のこれからを賭けようと思ったこと。そういった私の変遷を語りながら、一人親方の技術者としてなぜkintoneがふさわしいかを述べました。商談の場で要件定義と実装とテストが済んでしまうkintoneが、要件定義書や内部設計書やテスト仕様書、エビデンスの作成工数を省ける。それがなんでも一人でやらねばならない一人親方にとってどれだけ現状打破のツールとして魅力的か。そんなことを述べました。

CO-BA KORIYAMAは、演台と聴衆の皆様との距離が近い!私の前に喋られたお二方(サイボウズ社の後迫さん、テクネコ社の加藤さん)はどう思われたのか知りませんが、私にとっては聴衆との間合いがとても喋りやすかったです。前日のセミナーと差別するため、敢えてクダけた雰囲気で喋るつもりでしたが、会場の雰囲気もそれを手助けしてくれました。また、距離の近いことで、うなづきながら聞いて下さる方、笑って下さる方の反応もしっかり受け止められましたし。しかも、全く時計を見ずに喋ったにも拘らず、喋り終えたのは丁度30分きっかり。自分の持ち時間ぴったりでした。これには本気で驚きましたし、自分でも凄え!と思いました。

でも、良かったのはここまで。その後の質疑コーナーでは、まだまだ自らの知識不足を反省しました。トークでも触れたとおり、私が本格的にkintoneとの取り組みを再開したのは昨年秋。それまでの4年分の空白を埋めようと10ヶ月間追いつこうと努力してきました。しかし、まだまだ追いつけていないことが露呈しましたね。こなした案件数としてもまだまだ少ないと思わされました。

例えば、ユーザーが大量のアプリを作った際のアプリ管理はどうするか、というご質問。私自身が関わり納入したお客様は、こちらが提供したアプリ以外は自ら作らない方がほとんど。大量にアプリが存在する際のノウハウについて全く蓄積がなくお手上げでした。また、もう一つ抜かったと思ったのは、バッチ実行をどうするか、という質問でした。ここで私はkintoneのコマンドラインツールのことをすっかり失念してしまっておりました。また、スペース機能の活用についても、経験不足を反省しました。その辺りは、後迫さんと加藤さんにしっかりとフォローして頂きました。ありがとうございました。

こういった反省は、今後沢山の案件をこなして行くことで身につけるしかないでしょうね。kintone Café 福島 Vol.1で得た私の課題は、アプリのカスタマイズよりもむしろ運用面。この課題をどう埋めていくか。そこに努力を集中すべきという気付きが得られました。様々な業種、そして職種のお客様とkintoneを使ったシステムを組み上げて行く。そして運用面のノウハウを少しずつ積み上げて行く。これしかないと思います。

kintone Café 福島 Vol.1では、kintoneのβ版から関わったけれど、そのあと4年のブランクがあったことについて率直に述べました。任命を頂いてしばらくは肩書だけのエバンジェリストでしかなかったことも正直に言いました。しかし、昨秋から活動を再開し、今は伸びしろあるエバンジェリストになろうと努力していることも述べました。今さら経験を飾ろうとも思いませんし、運用ノウハウ不足をハッタリで補おうとも思いません。運用面の経験はこれからも引き続き積み上げ、そのためにもこれから頑張りで沢山のkintone案件を受注できるように頑張るしかないです。そして、その通りに郡山から帰ってからの二週間で、複数のkintone案件の引き合いを頂けています。ありがたいことです。

そういう意味でも、9/30の「脱Excel! 「kintone(キントーン)」で業務改善セミナー」とkintone Café 福島 Vol.1が地元の新聞で記事として頂いたことは、励みとなりました。両記事共に写真に私が登場しています。また、前者の記事には社名や私の名前までも載せて頂いています。仕事に関して新聞に名前が載ったのは初めてなので、正直嬉しいです。

あとは、今回のセミナーとkintone Caféで私が喋った内容が福島県の技術者や会社経営者の皆様にとって何らかの助けになったのであれば、これに勝る喜びはありません。そもそも私が何ゆえ郡山からの御招聘にお応えしたか。それは福島県の人々に何か貢献したいという思いです。これまで阪神・淡路大震災の被災者として、福島県の皆さんには何も出来ていませんでした。そういった私の心情についてはまたブログに書くつもりです。これからも今回のようなお誘いはなるべくお受けしたいと思っています。

その夜の懇親会は、とても初訪問の地とは思えぬほど、気持ちよく飲ませて頂きました。テーブルの下で足が攣るぐらいに。なのにうたた寝してしまうくらいに。

改めまして、kintone Café 福島 Vol.1の主催者の皆様、参加者の皆様、ありがとうございました!


kintoneセミナーで気をつけるべき点


先週末、セミナーの講師としてお招き頂き、郡山に行って参りました。
今回は、郡山地域ニューメディア・コミュニティ事業推進協議会様からのご依頼でした。題して、
「脱Excel! 「kintone(キントーン)」で業務改善セミナー」
https://www.techno-media.net6.or.jp/newmedia/advanced/1362

実は、首都圏を離れた場所でセミナーの講師としてお招き頂くのは始めてです。
今まで弊社の本拠である町田やkintone Café 神奈川、埼玉で話させて頂きましたが、今回は初めて首都圏を離れた場所でお話をする機会を頂きました。弊社にとっても代表の私にとっても初めての試みとなります。

dscn44582
こちらのエントリーでは、今回のセミナーを通じて得たことを書きます。一般の方向けにkintoneをプレゼンする際の反省。それが主な内容となります。また、今回はスライドとしてオンラインプレゼンサービスのslides.comを使いました。そういったオンラインプレゼンサービスでセミナーを行う際に気を付けるべき点も盛り込みました。ご参考になれば幸いです。

そもそも郡山市に訪問するのは、プライベートも含めて初めてです。福島県の皆様の県民性についても知らなければ、kintoneの普及率も知りませんでした。

事前に主宰者のご担当者からは、以下のような方向性を頂いておりました。
 ・社会人一般 ※IT系に限らず(業務改善・効率化・専門知識不要)
 ・中小企業の経営者・管理職(経営の効率化・コストダウン)
 ・ベンダー企業(顧客にkintoneを勧める立場の方)

つまり、技術者よりもユーザー様や導入決定に関わるご担当者向けの内容をご希望でした。

それを受け、どういうセミナー内容が相応しいか。考えました。

kintoneの特徴として真っ先に挙げられるのは、ドラッグ&ドロップによるフォーム作成の容易さと全体に通ずるシンプルな設計です。

それをフォーム設計の容易さ=工数削減の視点で話すか。
それとも、簡単にアプリが作れる楽しさに焦点を当てて語るか。

私が選んだのは両者のいいとこ取りです。

カリキュラムは以下の通り。
  ・excelとkintoneの比較
  ・アンケートアプリの作成実演
  ・アンケート結果の取り込み実演
  ・グラフの作成実演
  ・権限・通知・アクションの説明
  ・休憩
  ・Amazon連携・Google マップ連携の実装例の説明
  ・価格プラン・セキュリティについて
  ・Cybozu Developer Networkについて
  ・kintone Caféについて
  ・質疑応答

dscn44532
カリキュラムの冒頭では自己紹介をさせて頂き、その中で郡山の好印象を語りました。
続いてはExcelとkintoneの比較。kintoneを語る上で両者の比較は欠かせません。
その流れで、実際のアプリ開発の模様をアンケートアプリを例として取り上げました。アンケートアプリが出来た後は事前にアンケートアプリ用に用意しておいたデータの取り込み実演と、グラフ作成の実演。今回はハンズオンは行わず、私のデモをプロジェクタを通して皆様に披露しています。
休憩を挟んでからは、これまた事前に用意しておいたアプリで、住所を入力するとGoogle マップが反映する様子をごらん頂き、さらに書籍アプリとしてISBNを入力するとAmazonから取得した画像データなどが表示される実例の紹介。
その後は、価格の説明、セキュリティ、動作環境から、技術者向けのサポート体制についての説明を行い、質疑応答で幕を閉じました。

定員ぎりぎり、30名弱の方にご参加頂きました。主催者の皆様の努力には感謝です。また、終了後にはアンケートもご回答頂きました。

dscn4471
アンケートの内容からは、皆様の率直なご意見が伺え、とても参考になりました。30代の年齢の方々からは概ね高評価を頂きました。それはとても嬉しかったです。ですが、50代の方からは低評価でした。これには残念な思いでした。でも、高評価よりも低評価こそが自分を成長させてくれる原動力となります。率直にご意見が伺えたことにも感謝ですね。次回に向けてそこをどう活かすか。

以下に、私が受け止めた反省点と今後の改善点を書いてみます。

今回のセミナーでは、kintoneを紹介するのにExcelとの比較から入るのがよいと判断しました。実務に携わることの少ないであろう経営者の皆様にどうやってkintoneの利点を説き明かすか。パッケージシステムや基幹システムの替わりとしてkintoneを持ち出すのはよい選択とは思えません。それでは特定のシステムや業種に偏らざるを得ず、セミナーを聞きに来てくださっている皆様全てにとって相応しくありません。なのでセミナーで取り上げるとすればExcelを取り上げるのが相応しいと思いました。

では、Excelとkintoneはどう違うのか。何がkintoneの長所なのか。この点について、私は同時編集というキーワードに焦点を当てて話しました。

しかし、この点が経営者の方には分かりにくかったかもしれません。同時編集が出来ないことで困るのは、実務の方。そのあたりの不便さは経営者にとってはどうでもよいこと。kintoneの優位性を経営者の方に訴えるには、Excelとの比較には限界があるのかもしれません。30代の実務経験者にとっては理解頂けた点も、実務から離れて久しい経営者の方には利点と映らない。これは反省点ですね。

また、経営者は財務諸表で経営判断することに長けていらっしゃいます。ですが、kintoneのコスト削減効果を財務諸表でお見せすることに意味があるとは思えません。敢えて出すとすれば人件費の削減効果でしょうか。でも、それはアプリ開発のスピード感をデモすれば、容易にご納得いただける気がします。それもあって、今回のセミナーではその点には触れませんでした。ですが、コスト削減効果の見せ方は、今後の課題として考える必要があります。

また、もう一つの反省点は、画面が見えにくかった点です。なるべく文字を大きめに、一ページあたりの情報量も少なく抑えたつもりでした。事前に主催者のご担当者からはそれでもまだ一ページあたりの情報量を少なく、文字を大き目にというご指摘を頂いていました。また、文章の途中で単語が分割されてしまっている箇所についてもご指摘を頂きました。

ここで、冒頭に挙げたslides.comが登場します。ここのところ、私はセミナーや勉強会ではslides.comを使っています。パワーポイントに近い長方形のスライドがオンラインで簡単に出来るので重宝しています。

ですが、私が使っているプランは無料版なのです。無料版はCSS編集ができません。つまり画像のサイズが固定となります。ブラウザの機能でズームさせようにも、一旦貼り付けた画像はズームできません。一方、kintoneの画面を固定した画像はどうしても情報がつまっているため字も小さくなります。年配の方で、なおかつ会場の後ろに座っている方のためには拡大してお見せする必要があります。多分、年配の方からの不評の原因はこれが多かったと思われます。また、CSSが使えないことでもう一つ問題があります。それはテキスト自身の折り返し設定が、パソコンの画面解像度によって変動することです。つまり、セミナー資料を執筆し、改行箇所を調整しても、他のPCで確認すると違った箇所で改行されている可能性があるということです。特にテキスト要素自身の余白を多めに取っておかないと、変な改行が入るようです。これは今後注意したいところです。

CSSによる表示制御。それを実現するには、slides.comの場合Proプランにアップグレードする必要があります。今回は、改めてslides.comの無料版を使い続けることの限界を感じさせられました。
pricing-and-plans-slides

もちろん、オンラインプレゼンツールは他にもあります。Preziがよく知られていますよね。私も昨年、地元で始めてセミナーを行わせて頂いた際、Preziを使って資料作成しました。Preziはズームもできますし、各スライドを別々に管理するのではなく、一枚の大きな用紙の中を自由に移動させるという設計思想の下に作られています。これだとズームや改行の問題もうまく解消できそうです。ですが、私が使っているPreziのプランもまた、無料版です。Preziの無料版は、無条件に公開されてしまう制限があります。つまり、試作段階ですでに公開されてしまうのです。それもあって、Preziは使いませんでした。
%e6%96%99%e9%87%91%e3%83%97%e3%83%a9%e3%83%b3%e3%81%a8%e3%82%aa%e3%83%97%e3%82%b7%e3%83%a7%e3%83%b3
でも、Preziはズームが柔軟に出来るという点があり、やはり捨てがたい。

結局のところ、オンラインプレゼンツールにも一長一短があります。そして、セミナーの資料の質を上げるには有料版を使わざるをえないな、というのが今回の教訓の一つです。kintoneを説明する上で、画像のキャプチャは欠かせません。また、画像はズームして表示させないと、後ろのほうの席の方には見えづらい。ここの考慮をないがしろにすると、折角出席してくださった方のセミナーへの興味を殺ぐことになります。

これを解決するには、素直にパワーポイントをインストールするか、OpenOfficeのImpressを導入すれば済むのかもしれません。が、プレゼンソフト間でも相互の表示がきっちり同一になりません。それもまた、頭の痛いところです。プレゼン環境は早急に定めなければならないと思いました。

今回のセミナーで得た成果は多数ありました。そして、それと同じだけの反省点もありました。
それをまとめるとすれば、以下の三つです。

・Excelとの比較は実務担当者にしか有効でない。
・コスト削減効果を謳うには、 アプリの開発スピード以外に、実際の人件費削減効果を実際に試算したほうがよいかもしれない。
・プレゼンツールを厳選し、細かい画像もズームして表示させるような配慮が必要。

私にとって、今回の郡山商工会議所でのセミナーは、私自身にとってもすごく勉強になるものでした。セミナーの翌日は、kintone Café 福島 vol.1があったのですが、ホテルに帰ってからも資料の改訂作業にも熱がこもりました。

主催者の皆様、ご参加の皆様、今回は誠にありがとうございました!


2期目を迎え、2週間を経ての所感


4月に入ってから、ワークスタイルを少しだけ私の理想に近付けています。

Facebook静かな自宅では開発を集中して好きなだけ行い、一方では営業に商談に交流会にセミナーに自由に動き回る。といったような。もちろん、それにはリスクも覚悟も伴います。バランス感覚がより一層求められることは言うまでもなく。そんなわけで、今日から外回りの際はネクタイを締めることにしました。身だしなみ云々よりも自分のけじめのために。

今日は練馬のお客様の下へ訪問させて頂きました。社長様より経営哲学をじっくり聞かせて頂き、会社内も存分に見学させていただきました。実に見事な運営ぶりや、社員様やパート様との関係を実に上手に築いておられる姿に、感銘しました。アナログとITの長所が実にがっちりとかみ合っている運営を見るにつけ、非常に刺激になりました。ありがとうございました。

弊社も今後は人を雇って作業を分担していかねばなりません。すでに4月から一部の作業についてはお手伝いをお願いし始めています。でも人を自在に使いこなし、適材適所に相乗効果を発揮してもらうレベルに達するにはまだまだ私自身の精進が必要です。努力すべきと思っています。

4月に入ってからは、私自身の開発者としてのスキルをもう一度磨き直すべく、あえてタイトな納期のLAMP案件を請け、CUIでの操作も含めた開発・テストに没頭しています。一方ではkintoneエバンジェリストとしての動きを活発にしています。kintone Café 埼玉 vol.1の事務局を担当し、さらには別のkintone Caféの準備メンバーとしても動いています。そういった動きを重ねることで、人前でしゃべる技術を磨くという私自身の課題も少しずつ前に進んでいるように思います。今後もそのような場で登壇の場数を重ねるようにして、少しずつ経営者としての実践を身に付けて行きたいと思っています。

kintone案件のコンサル兼開発担当も2月から請けさせて頂いているのですが、そちらでも追加開発のご用命を頂きました。さらには今日、ありがたいことに別のお客様からもkintone案件をご用命頂きました。何とかkintoneエバンジェリストとしての顔でも、少しだけ見せられるようになってきたかな、と思っています。

ただ、私自身のマンパワーも限られています。開発とテストと営業と商談に加え、常駐先での作業もまだ続きます。トイロハさんへの記事アップは2週間ほど出稿出来ていません。トイロハさんは今後も続けていきたいと思っております。そんなわけで、交流会の出席はかなり厳選しなければ、と思っています。今日お話を聞かせて頂いた経営者の方も、交流会は厳選して参加されておられるとか。私も少し見直す時期が来ているのかもしれません。

でも、お蔭さまでなんとか4月からのワークスタイル変更もいい滑り出しを見せています。これも皆さまのお力添えの賜物と感謝しております。その場限りの儲け目当てではなく、お互いが永きにわたって良い関係を得られるよう、引き続き努力していきたいと思っております。

今後ともよろしくお願い申し上げます。