Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Array of all files and directories in the repository:
| lastCommitDaysAgo | Days since the last commit to this file | No |
| lastCommitDate | Date of the last commit to this file (ISO format) | No |
| topLevelIdentifiers | Number of top-level identifiers (classes, functions, variables) | No |
| githubActivity | GitHub activity data from open pull requests | No |
| custom | Object for custom metrics | No |

#### File Components
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export interface FileMetrics {
lastCommitDaysAgo?: number;
lastCommitDate?: string;
topLevelIdentifiers?: number;
testCoverageRatio?: number;
githubActivity?: Record<string, any>;
custom?: Record<string, any>;
}

Expand Down
16 changes: 16 additions & 0 deletions frontend/src/types/visualization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ export const DATA_SOURCES: DataSource[] = [
dataType: 'continuous',
applicableTo: 'edge',
},
{
id: 'github_activity',
name: 'GitHub Activity',
description: 'Activity level based on open PR changes',
color: '#f97316',
defaultWeight: 0,
category: 'git',
dataType: 'continuous',
applicableTo: 'node',
},
{
id: 'identifiers',
name: 'Identifiers',
Expand Down Expand Up @@ -250,6 +260,7 @@ export const DEFAULT_CONFIG: VisualizationConfig = {
filesystem_proximity: 0,
code_references: 0,
test_coverage_ratio: 0,
github_activity: 0,
},
threshold: 0,
includeDirectories: false,
Expand All @@ -267,6 +278,7 @@ export const DEFAULT_CONFIG: VisualizationConfig = {
filesystem_proximity: 0,
code_references: 0,
test_coverage_ratio: 0,
github_activity: 0,
},
threshold: 0,
includeDirectories: false,
Expand All @@ -284,6 +296,7 @@ export const DEFAULT_CONFIG: VisualizationConfig = {
filesystem_proximity: 30,
code_references: 70,
test_coverage_ratio: 0,
github_activity: 0,
},
threshold: 0,
includeDirectories: true,
Expand All @@ -301,6 +314,7 @@ export const DEFAULT_CONFIG: VisualizationConfig = {
filesystem_proximity: 0,
code_references: 100,
test_coverage_ratio: 0,
github_activity: 0,
},
threshold: 0,
},
Expand All @@ -317,6 +331,7 @@ export const DEFAULT_CONFIG: VisualizationConfig = {
filesystem_proximity: 0,
code_references: 0,
test_coverage_ratio: 100,
github_activity: 0,
},
threshold: 0,
includeDirectories: true,
Expand All @@ -333,6 +348,7 @@ export const DEFAULT_CONFIG: VisualizationConfig = {
semantic_similarity: 0,
filesystem_proximity: 0,
code_references: 100,
github_activity: 0,
},
threshold: 0,
},
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/utils/visualizationUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export interface ComputedNodeMetrics {
identifiers: number;
references: number;
test_coverage_ratio?: number;
github_activity?: number;
}

export interface ComputedLinkMetrics {
Expand Down Expand Up @@ -71,6 +72,12 @@ export const computeNodeMetrics = (data: RepositoryData): Map<string, ComputedNo
// For directories, use 'directory' as the file_type for categorical coloring
const fileType = file.type === 'directory' ? 'directory' : file.extension || 'unknown';

// Calculate GitHub activity score (0-1)
let githubActivityScore = 0;
if (fileMetrics.githubActivity) {
githubActivityScore = fileMetrics.githubActivity.activity_score || 0;
}

metrics.set(file.id, {
file_type: fileType,
file_size: file.size || 0,
Expand All @@ -79,6 +86,7 @@ export const computeNodeMetrics = (data: RepositoryData): Map<string, ComputedNo
identifiers: fileMetrics.topLevelIdentifiers || 0,
references: incomingReferences.get(file.id) || 0,
test_coverage_ratio: fileMetrics.testCoverageRatio,
github_activity: githubActivityScore,
});

// Add metrics for components (classes, functions, methods) - only for files, not directories
Expand All @@ -92,6 +100,7 @@ export const computeNodeMetrics = (data: RepositoryData): Map<string, ComputedNo
identifiers: fileMetrics.topLevelIdentifiers || 0,
references: incomingReferences.get(component.id) || 0,
test_coverage_ratio: fileMetrics.testCoverageRatio,
github_activity: githubActivityScore,
});
});
}
Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ semantic = [
"openai>=1.0.0",
"numpy>=1.20.0",
]
github = [
"requests>=2.25.0",
]

[project.scripts]
repo-visualizer = "src.repo_visualizer.cli:main"
Expand Down
47 changes: 44 additions & 3 deletions src/repo_visualizer/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
except ImportError:
NUMPY_AVAILABLE = False

from .github_client import GitHubClient
from .schema import (
Component,
File,
Expand All @@ -42,12 +43,19 @@
class RepositoryAnalyzer:
"""Analyzes a local git repository and generates visualization data."""

def __init__(self, repo_path: str):
def __init__(
self,
repo_path: str,
enable_github: bool = False,
github_token: Optional[str] = None,
):
"""
Initialize the repository analyzer.

Args:
repo_path: Path to the local git repository
enable_github: Whether to fetch GitHub activity data
github_token: GitHub personal access token for API requests
"""
self.repo_path = os.path.abspath(repo_path)
if not os.path.isdir(self.repo_path):
Expand All @@ -63,6 +71,18 @@ def __init__(self, repo_path: str):
self.relationships: List[Relationship] = []
self.relationship_counts: Dict[Tuple[str, str, str], int] = {}

# GitHub integration
self.enable_github = enable_github
self.github_client = None
self.github_activity_data: Optional[Dict[str, Dict]] = None

if enable_github:
try:
self.github_client = GitHubClient(github_token)
except ImportError as e:
print(f"Warning: {e}")
self.enable_github = False

# Load gitignore patterns
self.gitignore_spec = self._load_gitignore_patterns()

Expand All @@ -76,6 +96,12 @@ def analyze(self) -> RepositoryData:
Returns:
RepositoryData: The complete repository data structure
"""
# Fetch GitHub activity data if enabled
if self.enable_github and self.github_client:
self.github_activity_data = self.github_client.analyze_repository_activity(
self.repo_path
)

# Extract repository metadata
self._extract_metadata()

Expand Down Expand Up @@ -714,6 +740,12 @@ def _analyze_file_content(
if coverage_ratio is not None:
metrics["testCoverageRatio"] = coverage_ratio

# Add GitHub activity data if available
if self.github_activity_data and rel_path in self.github_activity_data:
if not metrics:
metrics = {}
metrics["githubActivity"] = self.github_activity_data[rel_path]

return components, metrics
except Exception as e:
print(f"Error analyzing file {rel_path}: {e}")
Expand Down Expand Up @@ -2339,15 +2371,24 @@ def default(self, obj):
print(f"Repository data saved to {output_path}")


def analyze_repository(repo_path: str, output_path: str) -> None:
def analyze_repository(
repo_path: str,
output_path: str,
enable_github: bool = False,
github_token: Optional[str] = None,
) -> None:
"""
Analyze a repository and generate visualization data.

Args:
repo_path: Path to the local git repository
output_path: Path to output JSON file
enable_github: Whether to fetch GitHub activity data
github_token: GitHub personal access token for API requests
"""
analyzer = RepositoryAnalyzer(repo_path)
analyzer = RepositoryAnalyzer(
repo_path, enable_github=enable_github, github_token=github_token
)
analyzer.analyze()
analyzer.save_to_file(output_path)

Expand Down
31 changes: 30 additions & 1 deletion src/repo_visualizer/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,18 @@ def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace:
action="store_true",
)

parser.add_argument(
"--github",
help="Enable GitHub integration to fetch PR activity data",
action="store_true",
)

parser.add_argument(
"--github-token",
help="GitHub personal access token (can also be set via GITHUB_TOKEN env var)",
type=str,
)

return parser.parse_args(args)


Expand Down Expand Up @@ -104,7 +116,24 @@ def main(args: Optional[List[str]] = None) -> int:

# Analyze repository
logger.info(f"Analyzing repository at {repo_path}")
analyze_repository(repo_path, output_path)

# GitHub integration
enable_github = parsed_args.github
github_token = parsed_args.github_token

if enable_github:
logger.info("GitHub integration enabled - fetching PR activity data")
if not github_token and not os.getenv("GITHUB_TOKEN"):
logger.warning(
"No GitHub token provided. API requests will be rate-limited."
)

analyze_repository(
repo_path,
output_path,
enable_github=enable_github,
github_token=github_token,
)

logger.info(f"Analysis complete. Output saved to {output_path}")
return 0
Expand Down
Loading
Loading