From 4b20d415745d81700dbed5f26f8b713247dce13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Caras?= Date: Tue, 30 Jul 2024 19:53:06 +0200 Subject: [PATCH] feat: allow manual sync through workflow_dispatch --- README.md | 4 ++ action.yml | 6 ++ dist/index.js | 146 ++++++++++++++++++++++++++++++++++---- src/freelo.ts | 2 +- src/index.ts | 189 +++++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 315 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index f357120..025d113 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ jobs: > It's okay to omit some of the `on` listening types, but it is needed to keep the `issue.opened` type, > because it creates the task and the comment to track the task across action runs. +If you want to sync your current repository, run the workflow manually (`workflow_dispatch`). **This will only sync open issues.** Make sure to check the available parameters below. + ### Parameters | Parameter | Description | Required | |--------------|--------------------------------------------------------------------------------------------------|-----------------------------| @@ -45,6 +47,8 @@ jobs: | github-token | GitHub token used to create issue comments; you should use the default `secrets.GITHUB_TOKEN` | Yes | | task-id | ID of the task under which subtasks will be created from issues | If `tasklist-id` is not set | | tasklist-id | ID of the tasklist where tasks will be created from issues | If `task-id` is not set | +| create-tasks-for-unknown | Whether to create new tasks for issues without a Freelo comment from actions bot; used only when running the workflow manually | If `task-id` is not set | +| manually-sync-new-comments | Whether to sync new comments when running the workflow manually | If `task-id` is not set | ### Linking GitHub users to Freelo users The action will look for a `freelo.txt` file inside of your `.github` folder (the one where Action workflows are stored). diff --git a/action.yml b/action.yml index a3f3fc2..bed1373 100644 --- a/action.yml +++ b/action.yml @@ -19,6 +19,12 @@ inputs: description: 'If not empty, will submit created issues as a subtask to the set task; either this or tasklist-id has to be entered.' tasklist-id: description: 'ID of the tasklist where tasks from GitHub issues should be created; either this or task-id has to be entered.' + create-tasks-for-unknown: + description: 'Whether to create a new task for issues that do not have a Freelo comment from the actions bot. Either true or false.' + required: true + manually-sync-new-comments: + description: 'Whether to sync comments when running manually' + required: true runs: using: 'node20' main: 'dist/index.js' \ No newline at end of file diff --git a/dist/index.js b/dist/index.js index 8c2b378..536a20f 100644 --- a/dist/index.js +++ b/dist/index.js @@ -45331,6 +45331,8 @@ var projectId = core.getInput("project-id"); var taskId = core.getInput("task-id"); var tasklistId = core.getInput("tasklist-id"); var token = core.getInput("github-token"); +var createTasks = core.getInput("create-tasks-for-unknown"); +var syncComments = core.getInput("manually-sync-new-comments"); if (!token) { core.setFailed("No GitHub token passed"); throw new Error("No GitHub token passed"); @@ -45365,7 +45367,121 @@ try { } try { if (!action) { - throw new Error("No action was passed"); + const issues = await octokit.rest.issues.listForRepo({ ...github.context.repo, state: "open" }); + for (const i of issues.data) { + const currentTaskId = await freeloId(i.number); + if (!currentTaskId || currentTaskId.length === 1) { + console.log(`${i.number} has no Freelo comment`); + if (!createTasks) { + continue; + } + console.log(`Creating task for ${i.number}`); + const taskComment2 = ` + Created by: ${freeloMention(i.user?.login ?? "Unknown")}
+ Description: ${import_sanitize_html.default(i.body ?? "None", sanitizeOptions)}
+ GitHub issue: #${i.number}
+ Assigned to: ${i.assignee ? `${freeloMention(i.assignee.login)}` : "Nobody"}
+ ${i.pull_request ? "This is a pull request
" : ""} + (This action was performed automatically, please do not edit this comment) + `; + const labels2 = []; + if (i.labels) { + for (const label of i.labels) { + if (typeof label === "string") { + labels2.push({ name: label }); + continue; + } + if (!label.name) + continue; + labels2.push({ + name: label.name, + color: label.color ?? `#${label.color}` + }); + } + } + const taskContent = { + name: i.title, + comment: { + content: taskComment2 + }, + labels: labels2 + }; + const res = await axios_default.post(!taskId ? `${apiEndpoint}/project/${projectId}/tasklist/${tasklistId}/tasks` : `${apiEndpoint}/task/${taskId}/subtasks`, taskContent, defaultOptions); + if (res.status > 399) { + console.error(res.data); + throw new Error("Got an error response from Freelo API"); + } + octokit.rest.issues.createComment({ + issue_number: i.number, + ...github.context.repo, + body: `Freelo task assigned: ${res.data.id}
Please do not edit or delete this comment as it is used to prevent duplication of tasks.` + }); + console.log(`Created task ${res.data.id} in Freelo`); + continue; + } + const titleRes = await axios_default.post(`${apiEndpoint}/task/${currentTaskId[1]}`, { + name: i.title + }, defaultOptions); + if (titleRes.status > 399) { + console.error(titleRes.data); + throw new Error("Got an error response from Freelo API"); + } + const taskComment = ` + Created by: ${freeloMention(i.user?.login ?? "Unknown")}
+ Description: ${import_sanitize_html.default(i.body ?? "None", sanitizeOptions)}
+ GitHub issue: #${i.number}
+ Assigned to: ${i.assignee ? `${freeloMention(i.assignee.login)}` : "Nobody"}
+ (This action was performed automatically, please do not edit this comment) + `; + const labels = []; + if (i.labels) { + for (const label of i.labels) { + if (typeof label === "string") { + labels.push({ name: label }); + continue; + } + if (!label.name) + continue; + labels.push({ + name: label.name, + color: label.color ?? `#${label.color}` + }); + } + } + const labelRes = await axios_default.post(`${apiEndpoint}/task-labels/add-to-task/${currentTaskId}`, { labels }, defaultOptions); + if (labelRes.status > 399) { + console.error(labelRes.data); + throw new Error("Got an error response from Freelo API"); + } + const bodyRes = await axios_default.post(`${apiEndpoint}/task/${currentTaskId[1]}/description`, { + comment: { content: taskComment }, + labels + }, defaultOptions); + if (bodyRes.status > 399) { + console.error(bodyRes.data); + throw new Error("Got an error response from Freelo API"); + } + console.log(`Updated issue ${i.number} in Freelo as ${currentTaskId}`); + if (!syncComments) { + continue; + } + for (const c of (await octokit.rest.issues.listComments({ ...github.context.repo, issue_number: i.number })).data) { + const taskComment2 = ` + Comment ${c.id} by: ${freeloMention(c.user?.login ?? "Unknown")}
+ ${import_sanitize_html.default(c.body ?? "Comment not found", sanitizeOptions)}
+ GitHub issue: #${i.number}
+ (This action was performed automatically, please do not edit this comment) + `; + const res = await axios_default.post(`${apiEndpoint}/task/${currentTaskId}/comments`, { + content: taskComment2 + }); + if (res.status > 399) { + console.error(res.data); + throw new Error("Got an error response from Freelo API"); + } + console.log(`Created comment ${res.data.id}`); + } + } } if (!email || !apiKey || !projectId) { throw new Error("You are missing a required parameter. Check the documentation for details."); @@ -45382,7 +45498,7 @@ try { GitHub issue: #${issue.number}
Assigned to: ${issue.assignee ? `${freeloMention(issue.assignee.login)}` : "Nobody"}
${issue.pull_request ? "This is a pull request
" : ""} - (This action was performed automatically) + (This action was performed automatically, please do not edit this comment) `; const labels = []; if (issue.labels) { @@ -45411,7 +45527,7 @@ try { } case "edited": { const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -45427,7 +45543,7 @@ try { Description: ${import_sanitize_html.default(issue.body ?? "None", sanitizeOptions)}
GitHub issue: #${issue.number}
Assigned to: ${issue.assignee ? `${freeloMention(issue.assignee.login)}` : "Nobody"}
- (This action was performed automatically) + (This action was performed automatically, please do not edit this comment) `; const labels = []; if (issue.labels) { @@ -45452,7 +45568,7 @@ try { } case "closed": { const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("No Freelo task ID identified"); break; } @@ -45464,12 +45580,12 @@ try { break; } case "reopened": { - const currentTaskid = await freeloId(issue.number); - if (!currentTaskid || currentTaskid.length === 0) { + const currentTaskId = await freeloId(issue.number); + if (!currentTaskId || currentTaskId.length === 1) { console.log("No Freelo task ID identified"); break; } - const res = await axios_default.post(`${apiEndpoint}/task/${currentTaskid[1]}/activate`, null, defaultOptions); + const res = await axios_default.post(`${apiEndpoint}/task/${currentTaskId[1]}/activate`, null, defaultOptions); if (res.status > 399) { console.error(res.data); throw new Error("Got an error response from Freelo API"); @@ -45480,7 +45596,7 @@ try { if (!github.context.payload.assignee || !userPairing[github.context.payload.assignee.login]) break; const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -45497,7 +45613,7 @@ try { if (!github.context.payload.assignee || !userPairing[github.context.payload.assignee.login]) break; const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -45525,7 +45641,7 @@ try { switch (action) { case "created": { const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -45533,7 +45649,7 @@ try { Comment ${comment.id} by: ${freeloMention(comment.user.login)}
${import_sanitize_html.default(comment.body, sanitizeOptions)}
GitHub issue: #${issue.number}
- (This action was performed automatically) + (This action was performed automatically, please do not edit this comment) `; const res = await axios_default.post(`${apiEndpoint}/task/${currentTaskId}/comments`, { content: taskComment @@ -45547,7 +45663,7 @@ try { } case "deleted": { const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -45571,7 +45687,7 @@ try { } case "edited": { const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -45589,7 +45705,7 @@ try { Comment ${comment.id} by: ${freeloMention(comment.user.login)}
${import_sanitize_html.default(comment.body, sanitizeOptions)}
GitHub issue: #${issue.number}
- (This action was performed automatically) + (This action was performed automatically, please do not edit this comment) `; const res = await axios_default.post(`${apiEndpoint}/comment/${findComment[0].id}`, { content: taskComment diff --git a/src/freelo.ts b/src/freelo.ts index 36f0bf5..95957a8 100644 --- a/src/freelo.ts +++ b/src/freelo.ts @@ -1,6 +1,6 @@ interface Label { name: string; - color: string; + color?: string; } interface NewTask { diff --git a/src/index.ts b/src/index.ts index ddaf2d5..9ed49b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,8 @@ const projectId = getInput("project-id"); const taskId = getInput("task-id"); const tasklistId = getInput("tasklist-id"); const token = getInput("github-token"); +const createTasks = getInput("create-tasks-for-unknown"); +const syncComments = getInput("manually-sync-new-comments") if (!token) { setFailed("No GitHub token passed"); @@ -82,8 +84,163 @@ async function freeloId(issue_number: number): Promise { try { if (!action) { - // TODO: Is run manually, check all issues - throw new Error("No action was passed"); + // Check what needs to be synced + const issues = await octokit.rest.issues.listForRepo({ ...context.repo, state:"open" }); + for (const i of issues.data) { + const currentTaskId = await freeloId(i.number); + if (!currentTaskId || currentTaskId.length === 1) { + console.log(`${i.number} has no Freelo comment`); + if (!createTasks) { + continue; + } + console.log(`Creating task for ${i.number}`); + const taskComment = ` + Created by: ${freeloMention(i.user?.login ?? "Unknown")}
+ Description: ${sanitize(i.body ?? "None", sanitizeOptions)}
+ GitHub issue: #${i.number}
+ Assigned to: ${i.assignee ? `${freeloMention(i.assignee.login)}` : "Nobody"}
+ ${i.pull_request ? "This is a pull request
" : ""} + (This action was performed automatically, please do not edit this comment) + `; + + const labels: Label[] = []; + if (i.labels) { + for (const label of i.labels) { + if(typeof(label) === "string"){ + labels.push({name:label}) + continue + } + if (!label.name) continue; + labels.push({ + name: label.name, + color: label.color ?? `#${label.color}`, + }); + } + } + + const taskContent: NewTask = { + name: i.title, + comment: { + content: taskComment, + }, + labels, + }; + + const res = await axios.post( + !taskId + ? `${apiEndpoint}/project/${projectId}/tasklist/${tasklistId}/tasks` + : `${apiEndpoint}/task/${taskId}/subtasks`, + taskContent, + defaultOptions, + ); + + // handle potential error response + if (res.status > 399) { + console.error(res.data); + throw new Error("Got an error response from Freelo API"); + } + + // create an issue comment so we can track if the task has been already created + octokit.rest.issues.createComment({ + issue_number: i.number, + ...context.repo, + body: `Freelo task assigned: ${res.data.id}
Please do not edit or delete this comment as it is used to prevent duplication of tasks.`, + }); + console.log(`Created task ${res.data.id} in Freelo`) + continue; + } + + // Update known tasks + // Edit task title + const titleRes = await axios.post( + `${apiEndpoint}/task/${currentTaskId[1]}`, + { + name: i.title, + }, + defaultOptions, + ); + if (titleRes.status > 399) { + console.error(titleRes.data); + throw new Error("Got an error response from Freelo API"); + } + const taskComment = ` + Created by: ${freeloMention(i.user?.login ?? "Unknown")}
+ Description: ${sanitize(i.body ?? "None", sanitizeOptions)}
+ GitHub issue: #${i.number}
+ Assigned to: ${i.assignee ? `${freeloMention(i.assignee.login)}` : "Nobody"}
+ (This action was performed automatically, please do not edit this comment) + `; + + const labels: Label[] = []; + if (i.labels) { + for (const label of i.labels) { + if(typeof(label) === "string"){ + labels.push({name:label}) + continue + } + if (!label.name) continue; + labels.push({ + name: label.name, + color: label.color ?? `#${label.color}`, + }); + } + } + + // Edit task labels + const labelRes = await axios.post( + `${apiEndpoint}/task-labels/add-to-task/${currentTaskId}`, + { labels }, + defaultOptions, + ); + if (labelRes.status > 399) { + console.error(labelRes.data); + throw new Error("Got an error response from Freelo API"); + } + + // Edit task body + const bodyRes = await axios.post( + `${apiEndpoint}/task/${currentTaskId[1]}/description`, + { + comment: { content: taskComment }, + labels, + }, + defaultOptions, + ); + if (bodyRes.status > 399) { + console.error(bodyRes.data); + throw new Error("Got an error response from Freelo API"); + } + + console.log(`Updated issue ${i.number} in Freelo as ${currentTaskId}`) + + // Sync comments + if(!syncComments) { + continue; + } + + for (const c of (await octokit.rest.issues.listComments({...context.repo,issue_number:i.number})).data) { + // New comment, add to Freelo task + const taskComment = ` + Comment ${c.id} by: ${freeloMention(c.user?.login ?? "Unknown")}
+ ${sanitize(c.body ?? "Comment not found", sanitizeOptions)}
+ GitHub issue: #${i.number}
+ (This action was performed automatically, please do not edit this comment) + `; + + // Create comment + const res = await axios.post( + `${apiEndpoint}/task/${currentTaskId}/comments`, + { + content: taskComment, + }, + ); + if (res.status > 399) { + console.error(res.data); + throw new Error("Got an error response from Freelo API"); + } + console.log(`Created comment ${res.data.id}`); + } + } } if (!email || !apiKey || !projectId) { throw new Error( @@ -108,7 +265,7 @@ try { GitHub issue: #${issue.number}
Assigned to: ${issue.assignee ? `${freeloMention(issue.assignee.login)}` : "Nobody"}
${issue.pull_request ? "This is a pull request
" : ""} - (This action was performed automatically) + (This action was performed automatically, please do not edit this comment) `; const labels: Label[] = []; @@ -151,7 +308,7 @@ try { case "edited": { const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -173,7 +330,7 @@ try { Description: ${sanitize(issue.body ?? "None", sanitizeOptions)}
GitHub issue: #${issue.number}
Assigned to: ${issue.assignee ? `${freeloMention(issue.assignee.login)}` : "Nobody"}
- (This action was performed automatically) + (This action was performed automatically, please do not edit this comment) `; const labels: Label[] = []; @@ -213,7 +370,7 @@ try { case "closed": { // Issue closed, finish task const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("No Freelo task ID identified"); break; } @@ -231,15 +388,15 @@ try { } case "reopened": { // Issue re-opened, activate task - const currentTaskid = await freeloId(issue.number); - if (!currentTaskid || currentTaskid.length === 0) { + const currentTaskId = await freeloId(issue.number); + if (!currentTaskId || currentTaskId.length === 1) { console.log("No Freelo task ID identified"); break; } // Reactivate const res = await axios.post( - `${apiEndpoint}/task/${currentTaskid[1]}/activate`, + `${apiEndpoint}/task/${currentTaskId[1]}/activate`, null, defaultOptions, ); @@ -258,7 +415,7 @@ try { ) break; const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -284,7 +441,7 @@ try { ) break; const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -330,7 +487,7 @@ try { case "created": { // New comment, add to Freelo task const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -339,7 +496,7 @@ try { Comment ${comment.id} by: ${freeloMention(comment.user.login)}
${sanitize(comment.body, sanitizeOptions)}
GitHub issue: #${issue.number}
- (This action was performed automatically) + (This action was performed automatically, please do not edit this comment) `; // Create comment @@ -359,7 +516,7 @@ try { case "deleted": { // Find comment, delete it const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -393,7 +550,7 @@ try { case "edited": { // Find comment, edit it const currentTaskId = await freeloId(issue.number); - if (!currentTaskId || currentTaskId.length === 0) { + if (!currentTaskId || currentTaskId.length === 1) { console.log("Comment found, but no Freelo task ID identified"); break; } @@ -419,7 +576,7 @@ try { Comment ${comment.id} by: ${freeloMention(comment.user.login)}
${sanitize(comment.body, sanitizeOptions)}
GitHub issue: #${issue.number}
- (This action was performed automatically) + (This action was performed automatically, please do not edit this comment) `; // Create comment