年単位で曜日指定の繰り返しタスクを作るscript action

以前のコードはよくないところがありました。
Is there a way to wait until method is completed?
↑のやり取りを元に修正しました。

こちらの投稿を見て、script actionを使えばできるかもと思い、挑戦してみました。

0から書くのはきついので、 AIに教えてもらいながら作ってみました。(AIのコードそのままでは動かないので、asanaAPIのリファレンスと睨めっこが必要です。)

なお、私の環境で何度か動作確認をしましたが、完全に動作しない可能性があることをご了承ください。また、script actionはenterprise以上が必要です。

APIを眺めていたら、タスクテンプレートからタスクを作成することもできるので、親タスク、サブタスク間で依存関係がある場合などは、こちらを使えば良さそうです。
気が向いたら、挑戦してみようと思います。

使い方

下の作り方に書いてあるカスタムフィールドで繰り返しの期間を設定します。全て、設定しないとルールは発動しません。
タスクを完了させます。すると、完了したタスクが複製され、指定した繰り返し期間後が期限のタスクが出来上がります。なお、サブタスクまで複製されますが、サブタスクに日付は入りません。

作り方

  1. 繰り返し期間を指定するカスタムフィールドを作成する
    n年後、月、第m週、曜日という、単一選択のカスタムフィールドを作成します。
    選択肢は、年は1-9くらい、月は1-12,mは1−5、曜日は月〜日に瀬亭します。

  2. タスクの完了をトリガにしたルールを作る
    下図のようなルールを作ります。

  3. script actionにコードを作成する
    スクリプトアクションにしたのコードを貼り付けて保存します。

/**
* What's in scope?
* 1. (string) project_gid, workspace_gid, task_gid (only if triggered on a task)
* 2. (function) log - this behaves like console.log and takes any number of parameters
* 3. (object) *ApiInstance - for each group of APIs, an object containing functions to call the APIs; for example:
*    tasksApiInstance.getTask(...)
*    goalsApiInstance.addFollowers(...)

* For more info, see https://github.com/Asana/node-asana
*/
function getNextDueDate(nYearsLater, month, occurrence, dayOfWeek){
  // change dayOfWeek to number
  const dayMap = {
    '日': 0,
    '月': 1,
    '火': 2,
    '水': 3,
    '木': 4,
    '金': 5,
    '土': 6
  };
  dayOfWeek = dayMap[dayOfWeek];
  // get the year after n years
  const yearAfterNyears = new Date().getFullYear() + nYearsLater;
  log("yearAfterNyears:",yearAfterNyears);
  // get the first day of month of the year after n years
  const firstDayOfMonth = new Date(yearAfterNyears, month - 1, 1);
  log("firstDayOfMonth", firstDayOfMonth);
  // get a week name of the first day of month of the year after n years
  const dayOfWeekOfFirstDay = firstDayOfMonth.getDay();
  log("dayOfWeekOfFirstDay", dayOfWeekOfFirstDay);
  // get days until first Nth day
  const daysUntilFirstNthDay = (dayOfWeek - dayOfWeekOfFirstDay + 7) % 7;
  log("daysUntilFirstNthDay", daysUntilFirstNthDay);
  // get the first Nth day
  const firstNthDay = 1 + daysUntilFirstNthDay;
  log("firstNthDay", firstNthDay);
  // get the target day
  const targetDay = firstNthDay + (occurrence - 1 ) * 7;
  log("targetDay", targetDay);
  // get date of due date of target
  const nextDueDate = new Date(yearAfterNyears, month - 1, targetDay);
  log("nextDueDate", nextDueDate);
  // check if nextDueDate exists
  if(nextDueDate.getMonth() !== month - 1){
    return null;
  }
  return nextDueDate.toISOString().slice(0, 10);
};

function createCustomFieldsDictionary(data) {
  const resultDictionary = data.reduce((acc, current) => {
    acc[current.name] = current.display_value;
    return acc
  },{});

  return resultDictionary;
}

async function updateTasks(body, taskGids) {
  // for...of ループで非同期処理を順番に実行
  for (const gid of taskGids) {
    log( taskGids);
    try {
      // await を使って、各API呼び出しが完了するのを待つ
      ret = await tasksApiInstance.updateTask(body, gid);
    } catch (error) {
      log(gid, error);
      // エラーハンドリング
    }
  }
};

async function getSubtasksGids(gid) {
  const subTasks = await tasksApiInstance.getSubtasksForTask(gid); // get subtasks from new task
  log(JSON.stringify(subTasks, null, 2));
  const subTasksGids = subTasks.data.map(item => item.gid); // get gid of subtasks
  return subTasksGids
};

const sleep = async (ms) => {
  log('Waiting...');
  const start = Date.now();
    let currentTime = Date.now();
      while(currentTime - start < ms){
        currentTime = Date.now();
      }
};

async function duplicateTaskWithWaitForSubtask(task_gid, body) {
  try {
    
    const newTask = await tasksApiInstance.duplicateTask(body, task_gid);
    log("duplicated:", JSON.stringify(newTask, null, 2));
    
    for(let i = 0; i < 10; i++){
      const jobStatus = await jobsApiInstance.getJob(newTask.data.gid);
      log(JSON.stringify(jobStatus, null, 2))
      if(jobStatus.data.status === "succeeded"){
        return newTask;
        };
      if(i < 9){
        const start = Date.now();
        await sleep(700);
        const currentTime = Date.now();
        log("start:", start, "end:", currentTime);
      }
    }
  } catch (error) {
    log('エラー:', error);
    throw error;
  }
}
async function run() {

  // Make an API request to get the triggered task and wait for the response
  // Use the task data in your script by referencing task.data.{attribute} ex: task.data.assignee
  let body = {};
  let ret = [];

  //get the task name
  const task = await tasksApiInstance.getTask(task_gid, {'opt_fields': "name,due_on,custom_fields"});
  log(JSON.stringify(task, null, 2));
  // create custum_fileds dictionary
  log(JSON.stringify(task.data.custom_fields, null, 2));
  const custom_fieldDictionary = createCustomFieldsDictionary(task.data.custom_fields);
  log(JSON.stringify(custom_fieldDictionary, null, 2),custom_fieldDictionary["曜日"]);
  
  //duplicat the task
  body = {
      "data": {
        "name": task.data.name,
        "include": ["notes","assignee","subtasks","attachments","tags","followers","projects","dependencies","parent"]
      }
    };
  const newTask = await duplicateTaskWithWaitForSubtask(task_gid, body);
  body = {
    "data": {
      "completed": false,
      "due_on":
        getNextDueDate(
          Number(custom_fieldDictionary["n年後"]),
          Number(custom_fieldDictionary["月"]),
          Number(custom_fieldDictionary["第m週"][1]),
          custom_fieldDictionary["曜日"]
        ) 
    }
  } ;
  ret = await updateTasks(body, [newTask.data.new_task.gid]);
  // set subtasks to incompleted
  const subTasksGids = await getSubtasksGids(newTask.data.new_task.gid);
  body = {  
    "data": {
      "completed": false
    }
  };
  ret = await updateTasks(body, subTasksGids)
}
  
// To function properly, the script must end with returning a Promise via run().
run();
5 Likes