Github Actions 101
This blog post walks through how we use GitHub Actions to automate fetching completed issues by label: bug counts, enhancements, and documentation updates.
In this guide, you’ll learn:
- What GitHub Actions are and how to set them up
- How to securely reference PAT tokens to access private repo data
- Breaking down each part of a GitHub Action workflow
- Why we use GraphQL for fetching data
- How to output JSON for frontend use
What are GitHub Actions?
GitHub Actions are automation workflows you can trigger directly in your GitHub repositories. Whether you want to run tests, deploy code, or automate administrative tasks (like tracking issues), Actions make it easy to build and deploy code automatically.
How it Works:
- Event-driven: Actions trigger on events (like pushing to a repo or opening an issue).
- Flexible: You can run custom scripts or use pre-built actions from the GitHub Marketplace.
- Scalable: Actions run on virtual environments (Ubuntu).
For our issue tracker, we use GitHub Actions to fetch the latest bug/enhancement data daily and store it in a JSON file that the frontend reads.
Setting Up GitHub Personal Access Tokens (PAT)
Since our repositories are private, we need a secure way to fetch issue data. GitHub Personal Access Tokens (PAT) grant fine-grained access to your repositories.
Steps to Generate a PAT:
- Go to Settings > Developer Settings > Personal Access Tokens on GitHub.
- Click Generate new token and select the following scopes:
repo
– Full control of private repositoriesread:org
– Read organization-level informationissues
– Read issues data
- Copy the token – this is your key for API access.
Storing and Referencing the PAT Securely:
- In your repository, go to Settings > Secrets and Variables > Actions.
- Click New Repository Secret and name it
PAT_TOKEN
. - In your GitHub Actions workflow, reference the secret like this:
TOKEN=${{ secrets.PAT_TOKEN }}
Breaking Down the GitHub Action Workflow
Now that our token is set up, let’s break down the GitHub Action workflow we use to track issues. Each repository (API, game, and website) has a separate workflow, outputting to its own JSON file.
.github/workflows
- api-issue-tracker.yml
- hoa-issue-tracker.yml
- website-issue-tracker.yml
public
- api-issue-count.json
- frontend-issue-count.json
- hoa-issue-count.json
Example Workflow
name: Track 3ee API Resolved Issues
on:
schedule:
- cron: '0 0 * * *' #Runs daily at midnight
workflow_dispatch: #Manual trigger
permissions:
contents: write
issues: read
jobs:
track-issues:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v3
- name: Fetch All Closed Issues (GraphQL)
run: |
TOKEN=${{ secrets.PAT_TOKEN }}
OWNER="3ee-Games"
REPO="3ee-api"
fetch_issues() {
LABEL=$1
END_CURSOR=$2
QUERY="{"query": "query {
repository(owner: \"$OWNER\", name: \"$REPO\") {
issues(first: 100, labels: [\"$LABEL\"], states: CLOSED, after: $END_CURSOR) {
totalCount
pageInfo {
endCursor
hasNextPage
}
}
}
}"}"
echo $QUERY
}
count_issues_by_label() {
LABEL=$1
TOTAL_COUNT=0
END_CURSOR="null"
while :; do
QUERY=$(fetch_issues "$LABEL" "$END_CURSOR")
RESPONSE=$(curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$QUERY" https://api.github.com/graphql)
BUG_COUNT=$(echo $RESPONSE | jq '.data.repository.issues.totalCount')
TOTAL_COUNT=$((TOTAL_COUNT + BUG_COUNT))
HAS_NEXT_PAGE=$(echo $RESPONSE | jq '.data.repository.issues.pageInfo.hasNextPage')
END_CURSOR=$(echo $RESPONSE | jq -r '.data.repository.issues.pageInfo.endCursor')
if [ "$HAS_NEXT_PAGE" != "true" ]; then
break
fi
done
echo "$TOTAL_COUNT"
}
BUG_COUNT=$(count_issues_by_label "bug")
DOC_COUNT=$(count_issues_by_label "documentation")
ENHANCEMENT_COUNT=$(count_issues_by_label "enhancement")
mkdir -p public
echo "{
"resolved_bugs": $BUG_COUNT,
"resolved_documentation": $DOC_COUNT,
"resolved_enhancements": $ENHANCEMENT_COUNT
}" > public/api-issue-count.json
- name: Commit and Push Issue Counts
run: |
git config user.name "github-actions"
git config user.email "actions@github.com"
git add public/api-issue-count.json
git commit -m "Update issue counts" || echo "No changes to commit"
git push
Why Use GraphQL?
GitHub’s REST API can be limiting when fetching large amounts of data. GraphQL allows us to request exactly the data we need in a single query, reducing the number of API calls.
- Efficiency: Fetches all labeled issues in one request.
- Pagination: Handles pagination gracefully using
endCursor
andhasNextPage
. - Customizable: We can adjust queries dynamically by passing labels or states.
In the next section, I’ll illustrate the difference between using GraphQL and REST, from the query to the response and how we can use that response to build a frontend component.
GraphQL returning exactly what we want
GraphQL returns only the requested fields (title, state, and labels). No unnecessary data is retrieved.
Query:
{
repository(owner: "3ee-Games", name: "3ee-api") {
issues(first: 10, labels: ["bug"], states: CLOSED) {
edges {
node {
title
state
labels(first: 5) {
edges {
node {
name
}
}
}
}
}
}
}
}
Output:
{
"data": {
"repository": {
"issues": {
"edges": [
{
"node": {
"title": "Fix login bug",
"state": "CLOSED",
"labels": {
"edges": [
{ "node": { "name": "bug" } }
]
}
}
},
{
"node": {
"title": "Resolve API timeout",
"state": "CLOSED",
"labels": {
"edges": [
{ "node": { "name": "bug" } },
{ "node": { "name": "urgent" } }
]
}
}
}
]
}
}
}
}
REST returning everything
REST returns everything by default (user info, timestamps, full issue body, comments, etc.), even if you don’t need it.
Query:
curl -s -H "Authorization: token $TOKEN"
"https://api.github.com/repos/3ee-Games/3ee-api/issues?state=closed&labels=bug"
Output:
[
{
"id": 123456789,
"node_id": "MDU6SXNzdWUxMjM0NTY3ODk=",
"title": "Fix login bug",
"state": "closed",
"labels": [
{
"id": 987654321,
"node_id": "MDU6TGFiZWw5ODc2NTQzMjE=",
"name": "bug",
"color": "d73a4a",
"default": true,
"description": "Something isn't working"
}
],
"locked": false,
"comments": 5,
"created_at": "2023-01-01T12:00:00Z",
"updated_at": "2023-01-05T15:30:00Z",
"closed_at": "2023-01-05T15:30:00Z",
"author_association": "CONTRIBUTOR",
"body": "Steps to reproduce the issue..."
}
]
This is why GraphQL is more efficient for fetching exactly what you need, reducing payload size and improving performance.
Outputting to JSON for Frontend Use
Each time the action runs, it generates a JSON file that looks like this:
{
"resolved_bugs": 41,
"resolved_documentation": 9,
"resolved_enhancements": 57
}
These files live in the public
folder and are read by frontend components to display live issue counts: https://github.com/3ee-Games/github-actions/tree/main/public
We can build a simple component that makes a call to those json files the actions created. In this example, we’ll log the response:
async function fetchIssueData() {
try {
const response = await fetch('https://raw.githubusercontent.com/3ee-Games/github-actions/refs/heads/main/public/api-issue-count.json');
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`);
}
const data = await response.json();
console.log('Resolved Bugs:', data.resolved_bugs);
console.log('Resolved Documentation Issues:', data.resolved_documentation);
console.log('Resolved Enhancements:', data.resolved_enhancements);
}
catch (error) {
console.error('Error fetching issue data:', error);
}
}
fetchIssueData();
📄 You can copy and paste this code in your browser’s console and it will return a json object.
The next step is to build out a frontend component using html and css. Below is the component that was built and used on the homepage of this website:
Issue Tracker
Keeping players informed about the ongoing improvements in our games is important. This tracker offers a behind-the-scenes look at bugs that have been fixed, new features that have been added, and other enhancements in progress. It provides a transparent view of the work being done to create the best experience possible.
Bugs Resolved |
---|
Games 0 |
API Services 0 |
Website 0 |
Enhancements |
---|
Games 0 |
API Services 0 |
Website 0 |
Documentation |
---|
Games 0 |
API Services 0 |
Website 0 |
Final Thoughts
GitHub Actions provide a powerful way to automate issue tracking and boost transparency. By integrating GraphQL and automating with cron jobs, we can stay organized and communicate our progress with players in real-time.
Ready to set up your own tracker? Fork our repo and start automating your project.