diff --git a/.github/workflows/activity-trigger.yml b/.github/workflows/activity-trigger.yml index 4372db2cf8..44930e3de7 100644 --- a/.github/workflows/activity-trigger.yml +++ b/.github/workflows/activity-trigger.yml @@ -22,9 +22,11 @@ permissions: jobs: Gather-Activity-Event-Information: runs-on: ubuntu-latest - if: github.repository == 'hackforla/website' + if: github.repository == 'xnealcarson/website' steps: - uses: actions/checkout@v6 + with: + token: ${{ secrets.HACKFORLA_ADMIN_TOKEN }} - name: Gather Event Details id: gather-event-details @@ -44,8 +46,31 @@ jobs: with: github-token: ${{ secrets.HACKFORLA_GRAPHQL_TOKEN != '' && secrets.HACKFORLA_GRAPHQL_TOKEN || github.token }} script: | + let needsUpdate = false; const activities = ${{ steps.gather-event-details.outputs.result }}; const script = require('./github-actions/activity-trigger/post-to-skills-issue.js'); for (const activity of activities) { - await script({github, context}, activity); + let result = await script({github, context}, activity); + if (result === true) { + needsUpdate = true; + } } + return needsUpdate; + + # Run `git pull` so that branch is current prior to next step + - if: ${{ steps.post-to-skills-issue.outputs.result == 'true' }} + name: Pull latest changes from gh-pages + run: git pull + + # Commits list of inactive members to repo for using in next step, and in one month + - if : ${{ steps.post-to-skills-issue.outputs.result == 'true' }} + name: Update Inactive Members JSON + id: update-inactive-members-json + uses: stefanzweifel/git-auto-commit-action@v7.1.0 + with: + # Glob pattern of file which should be added to the commit + file_pattern: github-actions/utils/_data/skills-directory.json + + # Optional commit message and author settings + commit_message: Update Skills Directory JSON + commit_author: GitHub Actions Bot diff --git a/github-actions/activity-trigger/post-to-skills-issue.js b/github-actions/activity-trigger/post-to-skills-issue.js index 8e20cefb5f..0d4154199a 100644 --- a/github-actions/activity-trigger/post-to-skills-issue.js +++ b/github-actions/activity-trigger/post-to-skills-issue.js @@ -5,6 +5,7 @@ const postComment = require('../utils/post-issue-comment'); const checkTeamMembership = require('../utils/check-team-membership'); const statusFieldIds = require('../utils/_data/status-field-ids'); const mutateIssueStatus = require('../utils/mutate-issue-status'); +const { lookupSkillsDirectory, updateSkillsDirectory } = require('../utils/skills-directory'); // `complexity0` refers `Complexity: Prework` label const SKILLS_LABEL = retrieveLabelDirectory("complexity0"); @@ -33,14 +34,35 @@ async function postToSkillsIssue({github, context}, activity) { console.log(`eventActor is undefined (likely a bot). Cannot post message...`); return; } - - // Get eventActor's Skills Issue number, nodeId, current statusId (all null if no Skills Issue found) - const skillsInfo = await querySkillsIssue(github, context, eventActor, SKILLS_LABEL); - const skillsIssueNum = skillsInfo.issueNum; - const skillsIssueNodeId = skillsInfo.issueId; - const skillsStatusId = skillsInfo.statusId; - const isArchived = skillsInfo.isArchived; + // Step 1: Try local directory lookup first + let needsUpdate = false; + let skillsInfo = lookupSkillsDirectory(eventActor); + + if (!skillsInfo) { + console.log(`No cached Skills Issue found for ${eventActor}, querying GitHub...`); + + // Step 2: Fallback to GitHub API + skillsInfo = await querySkillsIssue(github, context, eventActor, SKILLS_LABEL); + + // Step 3: Save result to local directory if found + if (skillsInfo && skillsInfo.issueNum) { + needsUpdate = true + } else { + console.log(` ⮡ No Skills Issue found for ${eventActor}. Cannot post message.`); + return; + } + } + // Get eventActor's Skills Issue number, nodeId, current statusId (all null if no Skills Issue found) + //const skillsIssueNum = skillsInfo.issueNum; + const skillsIssueNum = 17; + const skillsIssueNodeId = skillsInfo.issueId; + const skillsStatusId = skillsInfo?.statusId || 'unknown'; + const isArchived = skillsInfo?.isArchived || false; + const commentIdCached = skillsInfo?.commentId || null; // not used currently + + console.log(`skillsIssueNum: ${skillsIssueNum}, skillsIssueNodeId: ${skillsIssueNodeId}, skillsStatusId: ${skillsStatusId}, isArchived: ${isArchived}`); // only for debugging + // Return immediately if Skills Issue not found if (!skillsIssueNum) { console.log(` ⮡ Did not find Skills Issue for ${eventActor}. Cannot post message.`); @@ -48,57 +70,113 @@ async function postToSkillsIssue({github, context}, activity) { } console.log(` ⮡ Found Skills Issue for ${eventActor}: #${skillsIssueNum}`); - // Get all comments from the Skills Issue - let commentData; - try { - // https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#list-issue-comments - commentData = await github.request('GET /repos/{owner}/{repo}/issues/{issue_number}/comments', { - owner, - repo, - per_page: 100, - issue_number: skillsIssueNum, - }); - } catch (err) { - console.error(` ⮡ GET comments failed for issue #${skillsIssueNum}:`, err); - return; - } + let commentIdToUse = commentIdCached; + let commentFound = null; - // Find the comment that includes the MARKER text and append message - const commentFound = commentData.data.find(comment => comment.body.includes(MARKER)); + // Try cached comment ID first + if (commentIdCached) { + console.log(` ⮡ Found cached comment ID for ${eventActor}: ${commentIdCached}`); + try { + const { data: cachedComment } = await github.request( + 'GET /repos/{owner}/{repo}/issues/comments/{comment_id}', + { + owner, + repo, + comment_id: commentIdCached, + } + ); - if (commentFound) { - console.log(` ⮡ Found comment with MARKER...`); - const comment_id = commentFound.id; - const originalBody = commentFound.body; - const updatedBody = `${originalBody}\n${message}`; + if (cachedComment && cachedComment.body.includes(MARKER)) { + const updatedBody = `${cachedComment.body}\n${message}`; + await github.request('PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}', { + owner, + repo, + comment_id: commentIdCached, + body: updatedBody, + }); + console.log(` ⮡ Updated cached comment #${commentIdCached}`); + + } else { + commentIdToUse = null; + } + } catch (err) { + console.warn(` ⮡ Cached comment invalid or not found. Falling back to search.`, err); + commentIdToUse = null; // Force fallback path + } + } + + // Fallback — search for MARKER or create new comment + if (!commentIdToUse) { + console.log(` ⮡ Searching for activity comment marker...`); + let commentData; try { - // https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#update-an-issue-comment - await github.request('PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}', { - owner, - repo, - comment_id, - body: updatedBody - }); - console.log(` ⮡ Entry posted to Skills Issue #${skillsIssueNum}`); + commentData = await github.request( + 'GET /repos/{owner}/{repo}/issues/{issue_number}/comments', + { + owner, + repo, + per_page: 100, + issue_number: skillsIssueNum, + } + ); } catch (err) { - console.error(` ⮡ Something went wrong posting entry to #${skillsIssueNum}:`, err); + console.error(` ⮡ GET comments failed for issue #${skillsIssueNum}:`, err); + return; } - - } else { - console.log(` ⮡ MARKER not found, creating new comment entry with MARKER...`); - const body = `${MARKER}\n## Activity Log: ${eventActor}\n### Repo: https://github.com/hackforla/website\n\n##### ⚠ Important note: The bot updates this comment automatically - do not edit\n\n${message}`; - const commentPosted = await postComment(skillsIssueNum, body, github, context); - if (commentPosted) { - console.log(` ⮡ Entry posted to Skills Issue #${skillsIssueNum}`); + + commentFound = commentData.data.find((comment) => comment.body.includes(MARKER)); + + if (commentFound) { + console.log(` ⮡ Found comment with MARKER...`); + const comment_id = commentFound.id; + const originalBody = commentFound.body; + const updatedBody = `${originalBody}\n${message}`; + try { + await github.request('PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}', { + owner, + repo, + comment_id, + body: updatedBody, + }); + console.log(` ⮡ Entry posted to Skills Issue #${skillsIssueNum}`); + // Cache this comment ID + updateSkillsDirectory(eventActor, { commentId: comment_id }); + } catch (err) { + console.error(` ⮡ Something went wrong posting entry to #${skillsIssueNum}:`, err); + } + } else { + console.log(` ⮡ MARKER not found, creating new comment entry with MARKER...`); + const body = `${MARKER}\n## Activity Log: ${eventActor}\n### Repo: https://github.com/hackforla/website\n\n##### ⚠ Important note: The bot updates this comment automatically - do not edit\n\n${message}`; + try { + const { data: newComment } = await github.request( + 'POST /repos/{owner}/{repo}/issues/{issue_number}/comments', + { + owner, + repo, + issue_number: skillsIssueNum, + body, + } + ); + console.log(` ⮡ Entry posted to Skills Issue #${skillsIssueNum}`); + // Cache new comment ID + // updateSkillsDirectory(eventActor, { commentId: newComment.id }); + } catch (err) { + console.error(` ⮡ Failed to create new comment for issue #${skillsIssueNum}:`, err); + } } } + if (needsUpdate) { + console.log(` ⮡ Updating Skills Directory for ${eventActor}...`); + updateSkillsDirectory(eventActor, skillsIssueNum, skillsIssueNodeId, commentIdFound); + }; + // Only proceed if Skills Issue message does not include: 'closed', 'assigned', or isArchived if (!(message.includes('closed') || message.includes('assigned') || isArchived)) { // If eventActor is team member, open issue and move to "In progress" - const isActiveMember = await checkTeamMembership(github, context, eventActor, TEAM); - + //const isActiveMember = await checkTeamMembership(github, context, eventActor, TEAM); + const isActiveMember = true; if (isActiveMember) { try { await github.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', { @@ -121,4 +199,4 @@ async function postToSkillsIssue({github, context}, activity) { } -module.exports = postToSkillsIssue; \ No newline at end of file +module.exports = postToSkillsIssue; diff --git a/github-actions/utils/_data/skills-directory.json b/github-actions/utils/_data/skills-directory.json new file mode 100644 index 0000000000..41b42e677b --- /dev/null +++ b/github-actions/utils/_data/skills-directory.json @@ -0,0 +1,3 @@ +[ + +] diff --git a/github-actions/utils/skills-directory.js b/github-actions/utils/skills-directory.js new file mode 100644 index 0000000000..2a0e07bbe4 --- /dev/null +++ b/github-actions/utils/skills-directory.js @@ -0,0 +1,45 @@ +const fs = require('fs'); +const path = require('path'); + +const directoryPath = path.join(__dirname, '_data', 'skills-directory.json'); + +function loadDirectory() { + if (!fs.existsSync(directoryPath)) return {}; + return JSON.parse(fs.readFileSync(directoryPath, 'utf8')); +} + +function saveDirectory(data) { + fs.writeFileSync(directoryPath, JSON.stringify(data, null, 2)); +} + +function lookupSkillsDirectory(eventActor) { + const directory = loadDirectory(); + const result = directory.find(entry => entry.eventActor === eventActor); + return result || null; +} + +function updateSkillsDirectory(eventActor, skillsInfo) { + const directory = loadDirectory(); + // const result = directory.find(entry => entry.eventActor === eventActor); + // directory[eventActor] = skillsInfo; + const index = directory.findIndex(entry => entry.eventActor === eventActor); + if (index !== -1) { + directory[index] = { + eventActor, + issueNum, + nodeId, + commentId + }; + console.log(`directory[index]: ${JSON.stringify(directory[index])}`); // only for debugging + } else { + directory.push({ + eventActor, + issueNum, + nodeId, + commentId + }); + } + saveDirectory(directory); +} + +module.exports = { lookupSkillsDirectory, updateSkillsDirectory };