LANG SELRCT

Apps Script Reference  (Create: Create new Spreadsheet | Create new Apps Script

Monday, August 31, 2020

JIRAで権限スキームをコピーしたい


MISSION
JIRAの権限スキームをコピーする


PROCESS
プロジェクト設定
権限
アクション
権限を編集
権限スキーム
コピー



STEPS

STEP1
左メニューのプロジェクト設定をクリックします。


STEP2
権限をクリックします。


STEP3
右上のアクション > 権限を編集をクリックします。


STEP4
左メニューで権限スキームをクリックします。


STEP5
アクション列のコピーをクリックします。


コピーが作成されました。



参考 

プロジェクト権限を管理する > 権限スキームのコピー

Saturday, August 22, 2020

Slackの特定Channelへの投稿をSlack Appでキャッチしたい


MISSION
Slackの特定channelへの投稿をSlack Appでキャッチする


BEFORE
Outgoing Webhookでは簡単にできた。
(Slack Appが推奨されている)cf. Outgoing Webhooks


AFTER 
Slack Appで実現する。


PROCESS
Slack Appを新規作成
Request URL用にWebアプリを作成
Subscribe to bot events を設定
Workspaceにインストール
Channelにインストール


STEPS 

STEP1
① 左上にあるワークスペースのメニューを開きます。
② Settings & administration をクリックします。
③ Customize ワークスペース名 をクリックします。


STEP2
Configure apps をクリックします。


STEP3
Build をクリックします。


STEP4
Create New App をクリックします。


STEP5
  • App Name を決めます
  • Development Slack Workspaceを決めます
  • Create Appをクリックします


STEP6
Event Subscriptions をクリックします。


STEP7
Off をクリックして On にします。 

Google Apps ScriptでRequest URLに入れるWebアプリを作ります。



STEP18
STEP7の Request にGoogle Apps Scriptで作ったWebアプリのURLを貼り付けます。

するとこのようなエラーが出ると思います。
(params.challenge がうまく返せていない)


STEP19
エラーを回避するために
Google Apps Scriptのコードで5行目から8行目を//でコメントアウトします。


STEP20
公開 > ウェブアプリケーションとして導入 を選択します。


STEP21
Project Version を New にして、更新 します。


STEP22
Slackに戻って Retry をクリックします。

すると今度は エラーが消えて Verified になります。


STEP23
STEP19でコメントアウトした5行目から8行目を元に戻します。
(Verified後は11, 12行目をコメントアウトしても動くようです)


STEP24
STEP20〜21と同じくバージョンを更新します。
(修正したコードを反映する際にはバージョンの更新が必要なので忘れずに)


STEP25
Add Bot User Event をクリックします。


STEP26
message.channels を検索して選択します。


STEP27
右下の Save Changes を選択します。


STEP28
① 左のメニューから Basic Information をクリックします。
② Install your app to your workspace をクリックします。
③ Install App to Workspace をクリックします。


STEP29
Allow をクリックします。


STEP30
① Slackで対象Channelの Details を開きます。
② More をクリックします。
③ Add apps をクリックします。


STEP31
① 作成したアプリを検索します。
② 作成したアプリの右端の Add をクリックします。


Channelにアプリが追加されます。


STEP32
メッセージを投稿すると、Googleドライブの指定したフォルダにファイルが作成されます。

あとはWebアプリ側のコードを好きなように書き換えて処理を追加します。

これでOutgoing Webhookを使わずにSlack Appで代替することができました。





STEP8〜17までの手順

Google Apps ScriptでRequest URLに入れるWebアプリを作る



STEP8
Slack App からのメッセージを受け取るWebアプリを作ります。
このブログではGoogle Apps Scriptで作るため、新規プロジェクトを作成します。
このリンクから作成できます↓


STEP9
コード.gsをコピーして、1行目から貼り付けて上書きします。
(プロジェクト名は任意で変更します)



コード.gs 
/************************************
Slack Appからデータを受け取る
************************************/
function doPost(eventObj) {
  create_file(JSON.stringify(eventObj, null, 2));// eventObjの中身をGoogleドライブに出力する
  
  const contents = eventObj.postData.contents;
  create_file(contents);// eventObjのpostDataのcontents中身をGoogleドライブに出力する

  //https://api.slack.com/events/url_verificationの対応で、challengeを返してやる
  var params = JSON.parse(eventObj.postData.getDataAsString());
  return ContentService.createTextOutput(params.challenge);
}

/************************************
Googleドライブの指定フォルダにJSONファイルを保存する
************************************/
function create_file(json) {
  const content_type = "application/json";
  const file_name = "Slack Message";// Googleドライブに出力するファイルの名前
  const blob = Utilities.newBlob("", content_type, file_name);
  const file = blob.setDataFromString(json, "UTF-8");
  const folder = DriveApp.getFolderById("FOLDER_ID");// 出力したいGoogleドライブのフォルダID
  folder.createFile(file);
}




STEP10
公開 > ウェブアプリケーションとして導入 を選択します。


STEP11
① Deployするバージョンに任意でメッセージを入れる
② このWebアプリを実行するユーザーは自身のアカウントにする
③ Slack AppもこのWebアプリへアクセスできるように Anyone, even anonymous にする


STEP12
許可を確認 をクリックします。


STEP13
自身のアカウントを選択します。


STEP14
詳細 をクリックします。


STEP15
Slack Test App(安全ではないページ)に移動 をクリックします。


STEP16
許可 をクリックします。

STEP17
Current web app URL をコピーします。




関連記事



参考 


Start building Slack apps

Sunday, August 16, 2020

スプレッドシートにsetValue()で配列を入れる時はtoString()してやる

toString()しないと先頭の要素しか入らなかった。



MISSION
スプレッドシートにsetValue()で配列を入れる


BEFORE 
toString()しないと先頭の要素しか入らない。

  const array = ["hello", "hi"];
  sheet.getRange("A1").setValue(array);



AFTER 
toString()すると要素たちが入る。

  const array = ["hello", "hi"].toString();
  sheet.getRange("A1").setValue(array);



KEY
配列をtoString()する。



コード.gs
function dummy() {// これはうまくいかない
  const url = "SPREADSHEET_URL";
  const sheet = SpreadsheetApp.openByUrl(url).getSheets()[0];
  const array = ["hello", "hi"];
  sheet.getRange("A1").setValue(array);
}


function SetValueToString() {// これでうまくいった
  const url = "SPREADSHEET_URL";
const sheet = SpreadsheetApp.openByUrl(url).getSheets()[0]; const array = ["hello", "hi"].toString(); sheet.getRange("A1").setValue(array); }


参考 

setValue(value)

Saturday, August 15, 2020

テキストボックスの入力履歴をlocalStorageで読み書きしたい


MISSION 
テキストボックスの入力履歴をlocalStorageで読み書きする


KEY
  • localStorageに配列を保存する
  • 配列の要素数を固定する
  • 配列の先頭に要素を追加する
  • datalistのoptionを更新する


PROCESS

コード.gsとindex.htmlで、こういうUIを作って試してみる

  1. 入力ボックスに hi を入れる
  2. setをクリックする
    • localStorageに {"input_histories": ["hi"]} を保存する
    • 入力ボックスに hi をサジェストする
  3. 入力ボックスに hey を入れる
  4. setをクリックする
    • localStorageに {"input_histories": ["hey", "hi"]} を保存する
    • 入力ボックスに hey, hi をサジェストする
  5. 入力ボックスに yo を入れる
    • localStorageに {"input_histories": ["yo", "hey", "hi"]} を保存する
    • 入力ボックスに yo, hey, hi をサジェストする
  6. 入力ボックスに hello を入れる
    • localStorageに {"input_histories": ["hello", "yo", "hey"]} を保存する
    • 入力ボックスに hello, yo, hey をサジェストする



コード.gs 
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index");
}
意訳
この機能がやること
指定したHTMLファイルを表示する




index.html 
<!DOCTYPE html>
<html>
<body>
  <input id="tb" type="text" list="histories">
  <button id="set">set</button>
  <button id="get">getStorage</button>
  <button id="delete">deleteStorage</button>
  <div id="data_list_div"></div>
  
<script>
/************************************
グローバル
************************************/
const limit = 3;// 履歴に表示する件数
const storageKey = "input_histories";// 今回読み書きするlocalStorageのプロパティ名
const dataListDiv = "data_list_div";

/************************************
onclick
************************************/
elem("set").onclick = setClicked;
elem("get").onclick = () => getStorage(storageKey);// = function() {getStorage(storageKey);}
elem("delete").onclick = () => deleteStorage(storageKey);// = function() {deleteStorage(storageKey);}

/************************************
アプリ起動時に実行する
************************************/
createDatalist(getStorage(storageKey));// 入力履歴を読み込む
elem("tb").focus();// 初期フォーカス

/************************************
document.getElementByIdをelemで呼ぶ
************************************/
function elem(id) {
  return document.getElementById(id);
}

/************************************
setがクリックされたときの処理
************************************/
function setClicked() {
  const value = elem("tb").value;
  setStorage(value);
  elem("tb").value = "";// 空に戻す
  elem("tb").focus();// フォカース戻す
}

/************************************
localStorageに保存する
************************************/
function setStorage(value) {
  const key = storageKey;
  let values = getStorage(key);
  if(values) {
    removeExisting(value, values);
    removeTailLimit(values);
    values.unshift(value);// values = values.unshift(value) とやると配列の要素数が返ってくる
  } else {
    values = [value];
  }
  localStorage.setItem(key, JSON.stringify(values));
  createDatalist(values);
  console.log(value + " set");
  console.log(values);
}

/************************************
配列内にすでに同じ値があったら削除する
************************************/
function removeExisting(value, values) {
  const index = values.indexOf(value);
  if(index !== -1) {
    values = values.splice(index,1);
  }
}

/************************************
配列の要素がlimitに達している場合は、末尾を削除する
************************************/
function removeTailLimit(values) {
  if(values.length === limit) {
    values.pop();
  }
}

/************************************
localStorageからinput_historiesの値(ここでは配列)を取得する
************************************/
function getStorage(key) {
  const values = localStorage.getItem(key);
  console.log(values);
  return JSON.parse(values);
}

/************************************
localStorageからinput_historiesを削除する
************************************/
function deleteStorage(key) {
  const values = getStorage(key);
  localStorage.removeItem(key);
  removeChild(dataListDiv);
  console.log(key + ": " + values + " deleted");
}

/************************************
データリストを作成する
************************************/
function createDatalist(values) {
  if(values) {
    const parent = elem(dataListDiv);
    removeChild(dataListDiv);
    const datalist = document.createElement('datalist');
    datalist.setAttribute("id", "histories");
    for (let i = 0; i < values.length; i++) {
      const option = document.createElement('option');
      option.setAttribute("value", values[i]);
      datalist.appendChild(option);
    }
    parent.appendChild(datalist);
  }
}

/************************************
子要素を削除する
************************************/
function removeChild(id) {
  const targetElem = elem(id);
  for (let i = targetElem.childNodes.length - 1; i >= 0; i--) {
    targetElem.removeChild(targetElem.childNodes[i]);
  }
}

</script>
</body>
</html>


配列の要素数を固定にして新しい要素を先頭に追加したい


popとunshiftでやったときの備忘録。



MISSION 
配列の要素数を固定にして要素を追加する


KEY 
現在の配列の要素数がlimitに達していたら末尾を削除する
  if(values.length === limit) {
    values.pop();
  }


PROCESS
  1. 配列を用意しておく
    • const values = ["hey", "hi", "yo"]
  2. 要素数がlimitに達していたら末尾の要素を削除する
    • removeTailLimit(values)
  3. 配列の先頭に新しい要素を追加する
    • values.unshift("hello")



コード.gs
function myFunction() {
  const values = ["hey", "hi", "yo"];
  removeTailLimit(values);
  values.unshift("hello");
  Logger.log(values);
}


/************************************
配列の要素がlimitに達している場合は、末尾を削除する
************************************/
function removeTailLimit(values) {
  const limit = 3;
  if(values.length === limit) {
    values.pop();
  }
}



RESULT 

末尾の yo が削除されて、先頭に hello が追加された配列が出力されます。

localStorageのvalueを配列にして値を先頭に追加したい


MISSION 
localStorageのkeyをすべて取得する


KEY
1. JSON形式にする
保存する時にJSON形式にする
localStorage.setItem(key, JSON.stringify(values))

取得する時はオブジェクトにする
JSON.parse(values)


2. 先頭に値を追加する
values.unshift(value)
で配列の先頭にvalueを追加できる

ちなみに
values = values.unshift(value) とやると配列の要素数が返ってくる


PROCESS

コード.gsとindex.htmlで、こういうUIを作って試してみる

  1. 入力ボックスに値1を入れる
  2. setをクリックする
    • localStorageに {"input_histories": [値1]} を保存する
  3. getStorageをクリックする
    • console.logに [値1] を出力する
  4. 入力ボックスに値2を入れる
  5. setをクリックする
    1. localStorageに {"input_histories": [値2, 値1]} を保存する
  6. deleteをクリックする
    • {"input_histories": [値2, 値1]}をlocalStorageから削除する



コード.gs 
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index");
}
意訳
この機能がやること
指定したHTMLファイルを表示する




index.html 
<!DOCTYPE html>
<html>
<body>
  <input id="tb" type="text">
  <button id="set">set</button>
  <button id="get">getStorage</button>
  <button id="delete">deleteStorage</button>
  
<script>
elem("set").onclick = setClicked;
elem("get").onclick = getStorage;
elem("delete").onclick = deleteStorage;

function elem(id) {
  return document.getElementById(id);
}

function setClicked() {
  const value = elem("tb").value;
  setStorage(value);
  elem("tb").value = "";
  console.log(value + " set");
}

function setStorage(value) {
  const key = "input_histories";
  let values = getStorage();
  if(values) {
    values.unshift(value);// values = values.unshift(value) とやると配列の要素数が返ってくる
  } else {
    values = [value];
  }
  localStorage.setItem(key, JSON.stringify(values));
}

function getStorage() {
  const values = localStorage.getItem("input_histories");
  console.log(values);
  return JSON.parse(values);
}

function deleteStorage() {
  const key = "input_histories";
  localStorage.removeItem(key);
  console.log(key + " deleted");
}

</script>
</body>
</html>


localStorageのkeyをすべて取得したい


MISSION 
localStorageのkeyをすべて取得する


KEY
Object.keys(localStorage)
でlocalStorageのkeyを取得できる



PROCESS

コード.gsとindex.htmlで、こういうUIを作って試してみる

  1. 入力ボックスに値を入れる
  2. setをクリックする
    • localStorageに {値:日時} を保存する
  3. getKeysをクリックする
    • console.logに [値, 日時] を出力する
  4. localStorageから削除したい値を入れる
  5. deleteKeysをクリックする
    • 入力した値のデータ(プロパティ)をlocalStorageから削除する



コード.gs 
function doGet() {
  return HtmlService.createHtmlOutputFromFile("index");
}
意訳
この機能がやること
指定したHTMLファイルを表示する




index.html 
<!DOCTYPE html>
<html>
<body>
  <input id="tb" type="text">
  <button id="set">set</button>
  <button id="getKeys">getKeys</button>
  <button id="deleteKeys">deleteStorage</button>
  
<script>
elem("set").onclick = setClicked;
elem("getKeys").onclick = getkeys;
elem("deleteKeys").onclick = deleteStorage;

function elem(id) {
  return document.getElementById(id);
}

function setClicked() {
  const key = elem("tb").value;
  const datetime = new Date();
  setStorage(key, datetime);
  elem("tb").value = "";
  console.log(key + " set");
}

function setStorage(key, value) {
  const item = localStorage.setItem(key, value);
}

function getkeys() {
  const keys = Object.keys(localStorage);
  for(let i = 0; i < localStorage.length; i++) {
    const key = keys[i];
    const datetime = localStorage.getItem(key)
    console.log([key, datetime]);
  }
}

function deleteStorage() {
  const key = elem("tb").value;
  localStorage.removeItem(key);
  console.log(key + " deleted");
}

</script>
</body>
</html>

Sunday, August 2, 2020

SlackのOutgoing WebhooksでJIRAに課題を作りたい


MISSION
SlackのOutgoing Webhooksを利用して、ブログの新規投稿をJIRA課題として作成する。


KEY
SlackのRSS設定
Outgoing Webhooksの設定
JIRA API
Blogger


PROCESS
  1. SlackでブログのRSSフィードをキャッチする仕組みを作っておく→作り方
  2. ブログに新規投稿する
  3. キャッチした新規投稿をOutgoing Webhooksで外部Webアプリに送る
  4. 外部Webアプリでメッセージから情報を抽出する
  5. JIRA APIで課題を作る

この記事では
外部WebアプリをGoogle Apps Scriptで作成します。
このブログの新規投稿をキャッチして#generalに通知し、JIRAに課題を作った手順を書きます。

あくまで例として見ていただければと思います。


STEPS 

STEP1
Slackの#generalにBloggerの新規投稿を通知する仕組みを作っておきます


STEP2
Slackに通知されたメッセージを解析して、JIRAに課題を作るコードを作っておきます。
作り方→コード.gs


STEP3
#generalに通知されたメッセージをOutgoing WebhooksでGoogle Apps Scriptに送る仕組みを作っておきます。


STEP4
ブログに新規投稿します。


#generalに通知が来ます。


GoogleドライブにSlackデータを保存するとこのようなデータが確認できます。


JIRAに課題が作成されます。



関連記事

 


コード.gs
/************************************
環境によって異なる値は各自の環境に合わせて値を設定します
************************************/
const BASE_URL = 'https://SITENAME.atlassian.net/';// 対象のJIRA URL
const ISSUE_URL = BASE_URL + '/rest/api/3/issue/';// バージョンは2or3
const accountId = "ACCOUNT_ID";// 自分のJIRAアカウントのID
const FOLDER_ID = "FOLDER_ID";// create_fileで保存するときのGoogleドライブのフォルダID
const projectKey = "PROJECT_KEY";// JIRA課題を作成するプロジェクトキー

/************************************
SlackのOutgoing WebhookでこのプロジェクトのWebアプリのURLを指定している
eventObjにデータが飛んでくる
そのデータから欲しい値を抽出してJIRA APIに送ってissueを作成する
************************************/
function doPost(eventObj) {
  //create_file(JSON.stringify(eventObj, null, 2));// eventObjの中身を見たい時に使う
  const slackToJiraObj = parseSlackMessage(eventObj);
  postToJira(slackToJiraObj);
}

/************************************
Slackから送られてきたeventObj
{
  "parameter": {
    ...
    "text": "<https://www.pre-practice.net/2020/08/blog-post_2.html|テスト投稿>\nテスト用のブログ投稿です。ここに本文が入ります。",
  }
  ...
}

************************************/
function parseSlackMessage(eventObj) {
  const param = eventObj.parameter;
  const text = param["text"];
  const title = text.split("|")[1].split(">")[0];
  const url = text.split("<")[1].split("|")[0];
  const slackToJiraObj = {
    "text": text,
    "title": title,
    "url": url
  };
  return slackToJiraObj;
}

/************************************
飛んできたSlackのデータを元にJIRA課題を作る
************************************/
function postToJira(slackToJiraObj) {
  const payload = getPayload(slackToJiraObj);
  const options = getOptions(payload);
  const response = UrlFetchApp.fetch(ISSUE_URL, options);
  const key = JSON.parse(response)["key"];
  Logger.log(key);
}

/************************************
payloadを作って返す
************************************/
function getPayload(slackToJiraObj) {
  const data = {
    "project": {"key": projectKey},
    "issuetype": {"name": "Story"},
    "summary": slackToJiraObj["title"],
    "description": createDescription(slackToJiraObj["text"]),
    "reporter": {"id": accountId},
    // "customfield_10034": slackToJiraObj["url"]// JIRAにURLフィールドを作っておく
  };
  const fields = {"fields": data};
  const payload = JSON.stringify(fields);
  return payload;
}

/************************************
JIRA API v3ではdescriptionをこのように作るらしい
************************************/
function createDescription(text) {
  const description = {
    "type": "doc",
    "version": 1,
    "content": [
      {
        "type": "paragraph",
        "content": [
          {
            "text": text.replace(/。/g, "。\n"),// 。で改行を入れてやる
            "type": "text"
          }
        ]
      }
    ]
  }
  return description;
}

/************************************
optionsを作って返す
************************************/
function getOptions(payload) {
  const options = {
    "method": "post",
    "payload": payload,
    "contentType": "application/json",
    "headers": {"Authorization": " Basic " + getProp('jira_token')}
  }
  return options;
}

/************************************
スクリプトのプロパティから値を取得して返す
************************************/
function getProp(key) {
  return PropertiesService.getScriptProperties().getProperty(key);
}

/************************************
Googleドライブの指定フォルダにJSONファイルを保存する場合に使う
************************************/
function create_file(json) {
  const content_type = "application/json";
  const file_name = "Slack Message";
  const blob = Utilities.newBlob("", content_type, file_name);
  const file = blob.setDataFromString(json, "UTF-8");
  const folder = DriveApp.getFolderById(FOLDER_ID);
  folder.createFile(file);
}



Latest post

Extracting data from Google Sheets with regular expressions

Introduction Regular expressions are a powerful tool that can be used to extract data from text.  In Google Sheets, regular expressions ca...