こちらの投稿を見て、script actionを使えばできるかもと思い、挑戦してみました。
0から書くのはきついので、 AIに教えてもらいながら作ってみました。(AIのコードそのままでは動かないので、asanaAPIのリファレンスと睨めっこが必要です。)
なお、私の環境で何度か動作確認をしましたが、完全に動作しない可能性があることをご了承ください。また、script actionはenterprise以上が必要です。
APIを眺めていたら、タスクテンプレートからタスクを作成することもできるので、親タスク、サブタスク間で依存関係がある場合などは、こちらを使えば良さそうです。
気が向いたら、挑戦してみようと思います。
使い方
下の作り方に書いてあるカスタムフィールドで繰り返しの期間を設定します。全て、設定しないとルールは発動しません。
タスクを完了させます。すると、完了したタスクが複製され、指定した繰り返し期間後が期限のタスクが出来上がります。なお、サブタスクまで複製されますが、サブタスクに日付は入りません。
作り方
-
繰り返し期間を指定するカスタムフィールドを作成する
n年後、月、第m週、曜日という、単一選択のカスタムフィールドを作成します。
選択肢は、年は1-9くらい、月は1-12,mは1−5、曜日は月〜日に瀬亭します。 -
タスクの完了をトリガにしたルールを作る
下図のようなルールを作ります。 -
script actionにコードを作成する
スクリプトアクションにしたのコードを貼り付けて保存します。
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呼び出しが完了するのを待つ
await tasksApiInstance.updateTask(body, gid);
log(“aa”, 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
log(subTasksGids.length,“bbb”);
return subTasksGids
};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 = {};//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[“曜日”]);
const subtaskOfextask = await getSubtasksGids(task_gid)
log(“subtaskNomberOfextask”, subtaskOfextask);
//duplicat the task
body = {
“data”: {
“name”: task.data.name,
“include”: [“notes”,“assignee”,“subtasks”,“attachments”,“tags”,“followers”,“projects”,“dependencies”,“parent”]
}
};
const newTask = await tasksApiInstance.duplicateTask(body, task_gid);
log(JSON.stringify(newTask, null, 2));
// set due date to the new task and incompleted
const newTaskGid = newTask.data.new_task.gid
body = {
“data”: {
“completed”: false,
“due_on”:
getNextDueDate(
Number(custom_fieldDictionary[“n年後”]),
Number(custom_fieldDictionary[“月”]),
Number(custom_fieldDictionary[“第m週”][1]),
custom_fieldDictionary[“曜日”]
)
}
} ;
await updateTasks(body, [newTaskGid]);
// set new sub-tasks to incompleted
let subTasksGids = ;
let i = 0; // safty net for loop
while (i<10) {
subTasksGids = await getSubtasksGids(newTaskGid);
log(“i”, i, subTasksGids, subTasksGids.length, “/”, subtaskOfextask);
if(subTasksGids.length >= subtaskOfextask.length){
body = {
“data”: {
“completed”: false
}
};
await updateTasks(body, subTasksGids)
break;
}
i++
}
};
// To function properly, the script must end with returning a Promise via run().
run();