Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Branch Screen #8

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Dev
.db
__pycache__
.env

# Node
node_modules

# Bundles
app/static/scripts/*.bundle.js
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,20 @@ FROM python:3.10-slim
# Set the working directory inside the container
WORKDIR /app

# Install Node.js and npm
RUN apt-get update && apt-get install -y nodejs npm

# Copy requirements.txt and install dependencies globally
COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt

# Install npm dependencies
COPY package.json package-lock.json webpack.config.js ./
RUN npm install

# Add built assets to static folder
RUN npm run build

# Copy the app code to the container
COPY . .

Expand Down
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,26 @@ Flask server for displaying a quick development HUD (built specifically for use

## Development

Example ENV file

```bash
GITHUB_TOKEN=<your token>
GITHUB_REPOS=FRC-1721/ProjectHud
USERNAME_MAP=KenwoodFox:Joe,Kredcool:Keegan
GIT_COMMIT=Whatever
```

```shell
pipenv shell
flask run --debug
```

Some screens now require js libs so be sure to install and package those locally
```shell
npm run install
npm run build
```

## Deploy

Deployment is handled with docker.
Expand Down
3 changes: 3 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

app = Flask(__name__)

# Add globals
app.jinja_env.globals.update(enumerate=enumerate)


def create_app():
# Register Blueprints
Expand Down
51 changes: 47 additions & 4 deletions app/routes/screens.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
from flask import current_app as app
from flask import Blueprint, jsonify, render_template
from flask import Blueprint, jsonify, render_template, request

from app.services.github_service import GitHubService

Expand All @@ -9,9 +9,31 @@

@screens_bp.route("/api/screens")
def get_screens():
return jsonify(
{"version": os.getenv("GIT_COMMIT", None), "screens": ["/table", "/pending"]}
)
screens = ["/test"]

if (
len(app.github_service.latest_data["issues"])
+ len(app.github_service.latest_data["pull_requests"])
+ len(app.github_service.latest_data["milestones"])
> 0
):
screens.append("/table")
else:
app.logger.warning("Not including table screen (no data)")

if len(app.github_service.latest_data["pending_reviews"]) > 0:
screens.append("/pending")
else:
app.logger.warning("Not including pending reviews (no data)")

branch_graph = app.github_service.latest_data["branch_graph"]
if branch_graph:
for repo_name in branch_graph.keys():
screens.append(f"/branch?repo={repo_name}")
else:
app.logger.warning("Not including branch graph (no data)")

return jsonify({"version": os.getenv("GIT_COMMIT", None), "screens": screens})


@screens_bp.route("/test")
Expand Down Expand Up @@ -49,3 +71,24 @@ def pending_screen():
@screens_bp.route("/table")
def table_screen():
return render_template("table.html", **app.github_service.latest_data)


@screens_bp.route("/branch")
def branch_graph():
repo_name = request.args.get("repo")
if not repo_name:
return "Repository name is required", 400

branch_graph = app.github_service.latest_data["branch_graph"]

if repo_name not in branch_graph:
return f"Repository '{repo_name}' not found", 404

# Retrieve data for the specified repository
repo_data = branch_graph[repo_name]
if not repo_data:
return f"No data available for repository '{repo_name}'", 404

# Pass only the relevant repository data to the template
repo_data = branch_graph[repo_name]
return render_template("branch.html", repo_name=repo_name, repo_data=repo_data)
97 changes: 88 additions & 9 deletions app/services/github_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ def __init__(self, token, repos, username_mapping):
"issues_count": 0,
"pull_requests_count": 0,
"pending_reviews": [],
"branch_graph": {},
}

def fetch_data(self):
"""Main loop for updating GitHub data."""
while True:
self.update_general_data()
self.update_pending_reviews()

print("Updated github data.")
self.fetch_branch_graph()

time.sleep(90)

Expand All @@ -40,6 +40,8 @@ def update_general_data(self):
issues_data = []
pr_data = []

print("Updating General Data")

for repo_name in self.repos:
repo = self.gh.get_repo(repo_name)

Expand Down Expand Up @@ -96,18 +98,18 @@ def update_general_data(self):
}
)

self.latest_data = {
"milestones": milestones_data,
"issues": issues_data,
"pull_requests": pr_data,
"issues_count": len(issues_data),
"pull_requests_count": len(pr_data),
}
self.latest_data["milestones"] = milestones_data
self.latest_data["issues"] = issues_data
self.latest_data["pull_requests"] = pr_data
self.latest_data["issues_count"] = len(issues_data)
self.latest_data["pull_requests_count"] = len(pr_data)

def update_pending_reviews(self):
"""Fetch and update pending review requests data."""
pending_reviews = defaultdict(list)

print("Updating Pending Reviews")

for repo_name in self.repos:
repo = self.gh.get_repo(repo_name)

Expand Down Expand Up @@ -151,3 +153,80 @@ def update_pending_reviews(self):
{"name": name, "assignments": assignments}
for name, assignments in pending_reviews.items()
]

def fetch_branch_graph(self):
"""Fetch branch and commit data for branches with open PRs and the default branch."""
branch_graph = {}

print("Updating Branch Data")

for repo_name in self.repos:
repo = self.gh.get_repo(repo_name)
repo_data = {"repo_name": repo.full_name, "branches": []}

# Include the default branch
default_branch_name = repo.default_branch
default_branch_commits = repo.get_commits(sha=default_branch_name)[:20]
default_branch_info = {
"name": default_branch_name,
"commits": [
{
"sha": commit.sha,
"message": commit.commit.message,
"author": (
commit.commit.author.name
if commit.commit.author
else "Unknown"
),
"date": (
commit.commit.author.date.isoformat()
if commit.commit.author
else None
),
}
for commit in default_branch_commits
],
"target_pr": None, # Default branch is not targeted by a PR
}
repo_data["branches"].append(default_branch_info)

# Fetch branches with open pull requests
pull_requests = repo.get_pulls(state="open")
for pr in pull_requests:
branch_name = pr.head.ref
target_branch = pr.base.ref

# Fetch the last 20 commits for the branch
commits = repo.get_commits(sha=branch_name)[:20]
branch_info = {
"name": branch_name,
"commits": [
{
"sha": commit.sha,
"message": commit.commit.message,
"author": (
commit.commit.author.name
if commit.commit.author
else "Unknown"
),
"date": (
commit.commit.author.date.isoformat()
if commit.commit.author
else None
),
}
for commit in commits
],
"target_pr": target_branch,
}

repo_data["branches"].append(branch_info)

# Only include on branches that are not empty.
if len(repo_data["branches"]) > 0:
branch_graph[repo.full_name] = repo_data
else:
pass

# Update global data structure
self.latest_data["branch_graph"] = branch_graph
2 changes: 1 addition & 1 deletion app/static/scripts/autorefresh.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
document.addEventListener('DOMContentLoaded', () => {
// Configurable refresh duration (in seconds)
const REFRESH_DURATION = 25;
const REFRESH_DURATION = 5;

// HTML items
const iframe = document.getElementById("board-iframe");
Expand Down
30 changes: 30 additions & 0 deletions app/static/scripts/gitgraph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
document.addEventListener("DOMContentLoaded", () => {
const container = document.getElementById("gitgraph-container");
const gitgraph = GitgraphJS.createGitgraph(container);

// Example dynamic data (replace with real data from your backend)
const repoName = container.dataset.repoName;
const branches = JSON.parse(container.dataset.repoData); // Assuming backend passes JSON data

// Populate the graph
const branchObjects = {};
for (const [branchName, branchData] of Object.entries(branches)) {
branchObjects[branchName] = gitgraph.branch(branchName);
branchData.commits.forEach(commit => {
branchObjects[branchName].commit(commit.message);
});

// If the branch has a target PR, draw a dotted connection
if (branchData.target_pr) {
gitgraph.branch(branchData.target_pr).merge(branchObjects[branchName], "PR merge");
}
}

// Slow scroll to the bottom where branch heads are located
setTimeout(() => {
container.scrollTo({
top: container.scrollHeight,
behavior: "smooth",
});
}, 500); // Delay to ensure rendering is complete
});
Empty file added app/static/styles/branch.css
Empty file.
Loading