Skip to content

Triage Report to Slack #180

Triage Report to Slack

Triage Report to Slack #180

name: Triage Report to Slack
on:
schedule:
# Run every day at 9 AM UTC (adjust as needed)
- cron: '0 9 * * *'
workflow_dispatch: # Allow manual trigger
jobs:
report-triage-items:
runs-on: ubuntu-latest
steps:
- name: Get items needing triage
id: get-items
uses: actions/github-script@v7
with:
github-token: ${{ secrets.PROJECTS_PAT }}
script: |
const projectNumber = 75;
const org = 'podaac';
const statusFilter = 'needs:triage';
// First, get the project and status field info
const projectInfoQuery = `
query($org: String!, $number: Int!) {
organization(login: $org) {
projectV2(number: $number) {
id
title
fields(first: 20) {
nodes {
... on ProjectV2SingleSelectField {
id
name
options {
id
name
}
}
}
}
}
}
}
`;
const projectInfo = await github.graphql(projectInfoQuery, {
org: org,
number: projectNumber
});
const statusField = projectInfo.organization.projectV2.fields.nodes.find(
field => field.name === 'Status'
);
const statusFieldId = statusField?.id;
// Optimized query - only fetch essential fields
const itemsQuery = `
query($org: String!, $number: Int!, $cursor: String) {
organization(login: $org) {
projectV2(number: $number) {
items(first: 100, after: $cursor) {
pageInfo {
hasNextPage
endCursor
}
nodes {
id
fieldValueByName(name: "Status") {
... on ProjectV2ItemFieldSingleSelectValue {
name
}
}
content {
__typename
... on Issue {
number
title
url
repository {
name
owner {
login
}
}
createdAt
author {
login
}
}
... on PullRequest {
number
title
url
repository {
name
owner {
login
}
}
createdAt
author {
login
}
}
}
}
}
}
}
}
`;
let triageItems = [];
let hasNextPage = true;
let cursor = null;
// Fetch items with pagination, filtering as we go
while (hasNextPage) {
const result = await github.graphql(itemsQuery, {
org: org,
number: projectNumber,
cursor: cursor
});
const items = result.organization.projectV2.items.nodes;
console.log(`Statuses in this page: ${items.map(item => item.fieldValueByName?.name).join(', ')}`);
// Filter items by status immediately
const filteredBatch = items.filter(item => {
return item.fieldValueByName?.name === statusFilter && item.content;
});
triageItems = triageItems.concat(filteredBatch);
hasNextPage = result.organization.projectV2.items.pageInfo.hasNextPage;
cursor = result.organization.projectV2.items.pageInfo.endCursor;
}
// Fetch TVA team members and filter out their PRs
const tvaTeamQuery = `
query($org: String!) {
organization(login: $org) {
team(slug: "tva") {
members(first: 100) {
nodes {
login
}
}
}
}
}
`;
const tvaTeamResult = await github.graphql(tvaTeamQuery, { org: org });
const tvaMembers = new Set(
tvaTeamResult.organization.team.members.nodes.map(m => m.login)
);
tvaMembers.add("podaac-cicd");
console.log(`TVA team members (${tvaMembers.size}): ${[...tvaMembers].join(', ')}`);
triageItems = triageItems.filter(item => {
const isPR = item.content.__typename === 'PullRequest';
const author = item.content.author?.login;
const filtered = isPR && tvaMembers.has(author);
if (isPR) console.log(`PR author: ${author} — filtered out: ${filtered}`);
return !filtered;
});
// Format items for Slack
const formattedItems = triageItems.map(item => {
const content = item.content;
const type = content.__typename === 'Issue' ? 'Issue' : 'PR';
const repo = `${content.repository.owner.login}/${content.repository.name}`;
const createdAt = new Date(content.createdAt);
const date = createdAt.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
});
return {
type: type,
number: content.number,
title: content.title,
url: content.url,
repo: repo,
author: content.author?.login || 'unknown',
createdAt: createdAt,
displayDate: date
};
}).sort((a, b) => b.createdAt - a.createdAt); // Sort by date, newest first
// Create Slack message blocks
const blocks = [];
// Header
blocks.push({
type: "header",
text: {
type: "plain_text",
text: `🔍 New GitHub Tickets Needing Triage: ${formattedItems.length}`,
emoji: true
}
});
blocks.push({
type: "context",
elements: [
{
type: "mrkdwn",
text: `<https://github.com/orgs/podaac/projects/${projectNumber}|View Project Board>`
}
]
});
blocks.push({
type: "divider"
});
if (formattedItems.length === 0) {
blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: "✅ *No items need triage!* Great job keeping up with the backlog."
}
});
} else {
// Instructions section for team leads
blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: "*👋 Team Leads - Action Required*\n\nThe following items need triage. Click on each relevant ticket below and:\n\n1️⃣ Close the ticket if it can be done immediately, or add a green label team:<team name> to assign it to the appropriate team.\n"
}
});
blocks.push({
type: "divider"
});
// Add each item
formattedItems.forEach(item => {
const emoji = item.type === 'Issue' ? '🐛' : '🔀';
blocks.push({
type: "section",
text: {
type: "mrkdwn",
text: `${emoji} *<${item.url}|${item.repo}#${item.number}>* ${item.title}\n` +
`_Created by @${item.author} on ${item.displayDate}_`
}
});
});
// Add summary footer
const issueCount = formattedItems.filter(i => i.type === 'Issue').length;
const prCount = formattedItems.filter(i => i.type === 'PR').length;
blocks.push({
type: "divider"
});
blocks.push({
type: "context",
elements: [
{
type: "mrkdwn",
text: `📊 Summary: ${issueCount} issue${issueCount !== 1 ? 's' : ''} and ${prCount} PR${prCount !== 1 ? 's' : ''}`
}
]
});
}
// Set output for next step
const fs = require('fs');
const path = require('path');
const payload = { blocks: blocks };
const workspacePath = process.env.GITHUB_WORKSPACE || '.';
const filePath = path.join(workspacePath, 'slack-payload.json');
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2));
console.log(`Wrote payload to ${filePath}`);
core.setOutput('item_count', formattedItems.length);
console.log(`Found ${formattedItems.length} items needing triage`);
return formattedItems.length;
- name: Post to Slack
if: always() # Send always a message, to aknowledge properly routed tickets
run: |
curl -X POST "${{ secrets.SLACK_WEBHOOK_URL }}" \
-H "Content-Type: application/json" \
-d @${{ github.workspace }}/slack-payload.json
- name: Summary
run: |
echo "✅ Triage report sent to Slack"
echo "📊 Items needing triage: ${{ steps.get-items.outputs.item_count }}"