Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/activity-trigger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ jobs:
id: gather-event-details
uses: actions/github-script@v7
with:
github-token: ${{ secrets.HACKFORLA_GRAPHQL_TOKEN }}
# github-token defaults to `github.token` for 'pull_request_review'
github-token: ${{ secrets.HACKFORLA_GRAPHQL_TOKEN != '' && secrets.HACKFORLA_GRAPHQL_TOKEN || github.token }}
script: |
const script = require('./github-actions/activity-trigger/activity-trigger.js');
const activities = script({github, context});
Expand All @@ -41,7 +42,7 @@ jobs:
id: post-to-skills-issue
uses: actions/github-script@v7
with:
github-token: ${{ secrets.HACKFORLA_GRAPHQL_TOKEN }}
github-token: ${{ secrets.HACKFORLA_GRAPHQL_TOKEN != '' && secrets.HACKFORLA_GRAPHQL_TOKEN || github.token }}
script: |
const activities = JSON.parse(${{ steps.gather-event-details.outputs.result }});
const script = require('./github-actions/activity-trigger/post-to-skills-issue.js');
Expand Down
67 changes: 43 additions & 24 deletions github-actions/activity-trigger/activity-trigger.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,46 @@
async function activityTrigger({github, context}) {

let issueNum = '';
let assignee = '';
let timeline = '';

let eventName = context.eventName;
let eventAction = context.payload.action;
let eventActor = context.actor;

let eventObserver = '';
let eventPRAuthor = '';
let activities = [];

// Exclude all bot actors from being recorded as a guardrail against infinite loops
const EXCLUDED_ACTORS = ['HackforLABot', 'elizabethhonest', 'github-actions', 'github-advanced-security', 'github-pages', 'dependabot[bot]', 'dependabot-preview[bot]', 'dependabot', 'dependabot-preview'];
const EXCLUDED_ACTORS = [
"HackforLABot",
"elizabethhonest",
"dependabot",
"dependabot[bot]",
"github-actions",
"github-actions[bot]",
"github-advanced-security",
"github-advanced-security[bot]"
];

if (eventName === 'issues') {
issueNum = context.payload.issue.number;
eventUrl = context.payload.issue.html_url;
timeline = context.payload.issue.updated_at;
// If issue action is not opened and an assignee exists, then change
// the eventActor to the issue assignee, else retain issue author
assignee = context.payload.assignee?.login;
if (eventAction != 'opened' && assignee != null ) {
console.log(`Issue is ${eventAction}. Change eventActor => ${assignee}`);
eventActor = assignee;
} else {
eventActor = context.payload.issue.user.login;
}
// eventActor is the actor that directly causes or performs the eventAction
// eventObserver is the actor whose issue is being acted upon
if (eventAction === 'closed') {
// eventObserver is the assignee if exists, else is the issueAuthor
if (context.payload.issue.assignees?.length > 0) {
eventObserver = context.payload.issue.assignees[0].login; // aka assignee
} else {
eventObserver = context.payload.issue.user.login; // aka issueAuthor
}
let reason = context.payload.issue.state_reason;
eventActor = context.payload.issue.user.login;
eventAction = 'Closed-' + reason;
// eventActor is the assignee when eventAction is assigned/unassigned
} else if (eventAction === 'assigned' || eventAction === 'unassigned') {
eventActor = context.payload.assignee.login;
}
} else if (eventName === 'issue_comment') {
// Check if the comment is on an issue or a pull request
Expand All @@ -61,6 +72,11 @@ async function activityTrigger({github, context}) {
issueNum = context.payload.pull_request.number;
eventUrl = context.payload.review.html_url;
timeline = context.payload.review.updated_at;
eventActor = context.payload.review.user.login;
} else if (eventName === 'pull_request_review_comment') {
issueNum = context.payload.pull_request.number;
eventUrl = context.payload.comment.html_url;
timeline = context.payload.comment.updated_at;
}

// Return immediately if the issueNum is a Skills Issue- to discourage
Expand All @@ -81,12 +97,13 @@ async function activityTrigger({github, context}) {
'issues.assigned': 'assigned',
'issues.unassigned': 'unassigned',
'issue_comment.created': 'commented',
'pull_request_review.created': 'submitted review',
'pull_request_review.submitted': 'submitted review',
'pull_request_review_comment.created': 'commented',
'pull_request_comment.created': 'commented',
'pull_request.opened': 'opened',
'pull_request.PRclosed': 'closed',
'pull_request.PRmerged': 'merged',
'pull_request.reopened': 'reopened'
'pull_request_target.opened': 'opened',
'pull_request_target.PRclosed': 'closed',
'pull_request_target.PRmerged': 'merged',
'pull_request_target.reopened': 'reopened'
};

let localTime = getDateTime(timeline);
Expand All @@ -96,20 +113,22 @@ async function activityTrigger({github, context}) {
// Check to confirm the eventActor isn't a bot
const isExcluded = (eventActor) => EXCLUDED_ACTORS.includes(eventActor);
if (!isExcluded(eventActor)) {
console.log(`Not a bot. Message to post: ${message}`);
console.log(`Not a bot. Message to post: "${message}"`);
activities.push([eventActor, message]);
} else {
console.log(`eventActor: ${eventActor} likely a bot. Do not post`);
}

// Only if issue is closed, and eventActor != assignee, return assignee and message
if (eventAction.includes('Closed-') && (eventActor !== assignee)) {
message = `- ${assignee} issue ${action}: ${eventUrl} at ${localTime}`;
activities.push([assignee, message]);
// Only if issue is closed, and eventActor !== eventObserver, return eventObserver and message
if (eventAction.includes('Closed-') && (eventActor !== eventObserver)) {
message = `- ${eventObserver} was ${action}: ${eventUrl} at ${localTime}`;
activities.push([eventObserver, message]);
}
// Only if PRclosed or PRmerged, and PRAuthor != eventActor, return PRAuthor and message
if ((eventAction === 'PRclosed' || eventAction === 'PRmerged') && (eventActor != eventPRAuthor)) {
let messagePRAuthor = `- ${eventPRAuthor} PR was ${action}: ${eventUrl} at ${localTime}`;
if (!isExcluded(eventPRAuthor)) {
console.log(`Not a bot. Message to post: ${messagePRAuthor}`);
console.log(`Not a bot. Message to post: "${messagePRAuthor}"`);
activities.push([eventPRAuthor, messagePRAuthor]);
}
}
Expand Down Expand Up @@ -149,4 +168,4 @@ async function activityTrigger({github, context}) {

}

module.exports = activityTrigger;
module.exports = activityTrigger;
65 changes: 38 additions & 27 deletions github-actions/activity-trigger/post-to-skills-issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ async function postToSkillsIssue({github, context}, activity) {

// If eventActor undefined, exit
if (!eventActor) {
console.log(`eventActor is undefined (likely a bot). Cannot post message.`);
console.log(`eventActor is undefined (likely a bot). Cannot post message...`);
return;
}

Expand All @@ -42,9 +42,9 @@ async function postToSkillsIssue({github, context}, activity) {

// Return immediately if Skills Issue not found
if (skillsIssueNum) {
console.log(`Found Skills Issue for ${eventActor}: #${skillsIssueNum}`);
console.log(`Found Skills Issue for ${eventActor}: #${skillsIssueNum}`);
} else {
console.log(`Did not find Skills Issue for ${eventActor}. Cannot post message.`);
console.log(`Did not find Skills Issue for ${eventActor}. Cannot post message.`);
return;
}

Expand All @@ -58,8 +58,9 @@ async function postToSkillsIssue({github, context}, activity) {
per_page: 100,
issue_number: skillsIssueNum,
});
console.log(` ⮡ Found comment with MARKER...`);
} catch (err) {
console.error(`GET comments failed for issue #${skillsIssueNum}:`, err);
console.error(`GET comments failed for issue #${skillsIssueNum}:`, err);
return;
}

Expand All @@ -68,7 +69,6 @@ async function postToSkillsIssue({github, context}, activity) {
const commentFoundId = commentFound ? commentFound.id : null;

if (commentFound) {
console.log(`Found comment with MARKER: ${MARKER}`);
const commentId = commentFoundId;
const originalBody = commentFound.body;
const updatedBody = `${originalBody}\n${message}`;
Expand All @@ -80,37 +80,48 @@ async function postToSkillsIssue({github, context}, activity) {
commentId,
body: updatedBody
});
console.log(`Success! Entry posted to Skills Issue #${skillsIssueNum}`);
} catch (err) {
console.error(`Something went wrong updating comment:`, err);
console.error(`Something went wrong posting entry:`, err);
}

} else {
console.log(`MARKER not found in comments, creating new comment with MARKER...`);
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}`;
await postComment(skillsIssueNum, body, github, context);
const commentPosted = await postComment(skillsIssueNum, body, github, context);
if (commentPosted) {
console.log(`Success! Entry posted to Skills Issue #${skillsIssueNum}`);
}
}

// If eventActor is team member, open issue and move to "In progress". Else, close issue
const isActiveMember = await checkTeamMembership(github, context, eventActor, TEAM);
let skillsIssueState = "closed";
// Do not move or reopen Skills Issue if message includes the string 'closed'
if ((!message.includes('closed')) || (!message.includes('assigned'))) {

// If eventActor is team member, open issue and move to "In progress"
const isActiveMember = await checkTeamMembership(github, context, eventActor, TEAM);

if (isActiveMember) {
skillsIssueState = "open";
// Update item's status to "In progress (actively working)" if not already
if (skillsIssueNodeId && skillsStatusId !== IN_PROGRESS_ID) {
await mutateIssueStatus(github, context, skillsIssueNodeId, IN_PROGRESS_ID);
if (isActiveMember) {
try {
await github.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', {
owner,
repo,
issue_number: skillsIssueNum,
state: "open",
});
console.log(` ⮡ Re-opened issue #${skillsIssueNum}`);
// Update item's status to "In progress (actively working)" if not already
if (skillsIssueNodeId && skillsStatusId !== IN_PROGRESS_ID) {
const statusMutated = await mutateIssueStatus(github, context, skillsIssueNodeId, IN_PROGRESS_ID);
if (statusMutated) {
console.log(` ⮡ Changed issue #${skillsIssueNum} to "In progress"`);
}
}
} catch (err) {
console.error(` ⮡ Failed to update issue #${skillsIssueNum} state:`, err);
}
}
}
try {
await github.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', {
owner,
repo,
issue_number: skillsIssueNum,
state: skillsIssueState,
});
} catch (err) {
console.error(`Failed to update issue #${skillsIssueNum} state:`, err);
}

}

module.exports = postToSkillsIssue;
module.exports = postToSkillsIssue;
13 changes: 9 additions & 4 deletions github-actions/utils/mutate-issue-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,15 @@
};

try {
await github.graphql(mutation, variables);
} catch (error) {
throw new Error('Error in mutateIssueStatus() function: ' + error);
}
const result = await github.graphql(mutation, variables);
// Return true if mutation was successful
return true;
} catch (err) {
if (err.message.includes('archived')) {
console.log(` ⮡ Issue is archived- cannot change status`);
} else
throw new Error(' ⮡ Error in mutateIssueStatus() function: ' + err);
}
}

module.exports = mutateIssueStatus;
1 change: 1 addition & 0 deletions github-actions/utils/post-issue-comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ async function postComment(issueNum, comment, github, context) {
issue_number: issueNum,
body: comment,
});
return true;
} catch (err) {
throw new Error(err);
}
Expand Down