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