-
Notifications
You must be signed in to change notification settings - Fork 85
feat: Add script for reporting maintenance chart pass/fail coverage #1008
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
Merged
sarina
merged 1 commit into
openedx:main
from
jswope00:jswope00/maintenance_chart_report
Apr 16, 2025
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,211 @@ | ||
| """Maintenance Chart Checker | ||
|
|
||
| This script analyzes maintenance charts in reStructuredText (.rst) files. It recursively scans | ||
| through directories looking for .rst files containing maintenance charts in the format: | ||
|
|
||
| **Maintenance chart** | ||
|
|
||
| +--------------+-------------------------------+----------------+--------------------------------+ | ||
| | Review Date | Working Group Reviewer | Release |Test situation | | ||
| +--------------+-------------------------------+----------------+--------------------------------+ | ||
| | 2025-04-13 | reviewer | Release | Pass/Fail | | ||
| +--------------+-------------------------------+----------------+--------------------------------+ | ||
|
|
||
| The script generates a summary report showing: | ||
| - Total number of files with maintenance charts in each directory | ||
| - Number of empty maintenance charts | ||
| - Number of charts with passing test situations | ||
| - Number of charts with failing test situations | ||
|
|
||
| Usage: | ||
| # Run from current directory (default depth=1) | ||
| python maintenance_chart_checker.py | ||
|
|
||
| # Run from specific directory with custom depth | ||
| python maintenance_chart_checker.py path/to/docs --depth 2 | ||
|
|
||
| # Show full directory paths (depth=0) | ||
| python maintenance_chart_checker.py --depth 0 | ||
|
|
||
| # Show help | ||
| python maintenance_chart_checker.py -h | ||
|
|
||
| Example output with depth=1: | ||
| ------------------------------------------------------------ | ||
| | Folder | Total | Empty | Passed | Failed | | ||
| ------------------------------------------------------------ | ||
| | educators | 12 | 3 | 5 | 4 | | ||
| | faculty | 8 | 1 | 4 | 3 | | ||
| ------------------------------------------------------------ | ||
|
|
||
| Example output with depth=2: | ||
| ------------------------------------------------------------ | ||
| | Folder | Total | Empty | Passed | Failed | | ||
| ------------------------------------------------------------ | ||
| | educators/how-tos | 8 | 2 | 4 | 2 | | ||
| | educators/reference | 4 | 1 | 1 | 2 | | ||
| | faculty/guides | 8 | 1 | 4 | 3 | | ||
| ------------------------------------------------------------ | ||
| """ | ||
|
|
||
| import os | ||
| import argparse | ||
| from typing import Dict, List, NamedTuple | ||
| from collections import defaultdict | ||
|
|
||
| class MaintenanceStatus(NamedTuple): | ||
| is_empty: bool | ||
| has_fail: bool | ||
| has_pass: bool | ||
| file_path: str | ||
|
|
||
| def parse_maintenance_chart(content: str) -> MaintenanceStatus | None: | ||
| """Parse the maintenance chart in the content and return its status.""" | ||
| # Check if maintenance chart exists | ||
| if "**Maintenance chart**" not in content: | ||
| return None | ||
|
|
||
| # Split content into lines and find the maintenance chart section | ||
| lines = content.split('\n') | ||
| try: | ||
| chart_start = next(i for i, line in enumerate(lines) if "**Maintenance chart**" in line) | ||
| except StopIteration: | ||
| return None | ||
|
|
||
| # Find both header separator lines | ||
| try: | ||
| separators = [i for i in range(chart_start, len(lines)) | ||
| if '+-' in lines[i] and '-+' in lines[i]] | ||
| if len(separators) < 2: # Need both top and bottom separator | ||
| return None | ||
| first_data_idx = separators[1] + 1 # Take the line after the second separator | ||
| except (StopIteration, IndexError): | ||
| return None | ||
|
|
||
| # Get the first data row | ||
| if first_data_idx >= len(lines): | ||
| return None | ||
|
|
||
| first_data_row = lines[first_data_idx].strip() | ||
|
|
||
| # Check if the row is empty (just separators and spaces) | ||
| is_empty = all(cell.strip() in ['|', '', ' '] for cell in first_data_row.split('|')) | ||
|
|
||
| # Look for pass/fail in test situation column (last column) | ||
| columns = [col.strip().lower() for col in first_data_row.split('|') if col.strip()] | ||
| if not columns: # Empty row | ||
| return MaintenanceStatus(True, False, False, "") | ||
|
|
||
| # The test situation is in the last column | ||
| test_situation = columns[-1] if len(columns) >= 4 else "" | ||
| has_fail = 'fail' in test_situation.lower() | ||
| has_pass = 'pass' in test_situation.lower() | ||
|
|
||
| return MaintenanceStatus(is_empty, has_fail, has_pass, "") | ||
|
|
||
| def get_folder_path(path: str, start_path: str, depth: int) -> str: | ||
| """Get the directory path at specified depth relative to start_path.""" | ||
| rel_path = os.path.relpath(path, start_path) | ||
| if depth == 0: | ||
| return rel_path | ||
| parts = rel_path.split(os.sep) | ||
| return os.sep.join(parts[:depth]) if depth < len(parts) else rel_path | ||
|
|
||
| def scan_directory(start_path: str, depth: int) -> Dict[str, List[MaintenanceStatus]]: | ||
| """Recursively scan directory for .rst files and analyze maintenance charts.""" | ||
| results = defaultdict(list) | ||
|
|
||
| for root, _, files in os.walk(start_path): | ||
| rst_files = [f for f in files if f.endswith('.rst')] | ||
| if not rst_files: | ||
| continue | ||
|
|
||
| for file in rst_files: | ||
| file_path = os.path.join(root, file) | ||
| try: | ||
| with open(file_path, 'r', encoding='utf-8') as f: | ||
| content = f.read() | ||
| status = parse_maintenance_chart(content) | ||
| if status is not None: # Only include files with maintenance charts | ||
| folder_path = get_folder_path(root, start_path, depth) | ||
| results[folder_path].append(MaintenanceStatus( | ||
| status.is_empty, | ||
| status.has_fail, | ||
| status.has_pass, | ||
| file_path | ||
| )) | ||
| except Exception as e: | ||
| print(f"Error processing {file_path}: {e}") | ||
|
|
||
| return results | ||
|
|
||
| def print_report(results: Dict[str, List[MaintenanceStatus]]): | ||
| """Print a formatted report table of the maintenance chart analysis.""" | ||
| print("\nMaintenance Chart Analysis Report") | ||
| print("================================\n") | ||
|
|
||
| # Print table header | ||
| header = "| {:<30} | {:>8} | {:>8} | {:>8} | {:>8} |".format( | ||
| "Folder", "Total", "Empty", "Passed", "Failed" | ||
| ) | ||
| separator = "-" * len(header) | ||
|
|
||
| print(separator) | ||
| print(header) | ||
| print(separator) | ||
|
|
||
| # Print table rows | ||
| for folder, statuses in sorted(results.items()): | ||
| total = len(statuses) | ||
| empty = sum(1 for s in statuses if s.is_empty) | ||
| passed = sum(1 for s in statuses if s.has_pass) | ||
| failed = sum(1 for s in statuses if s.has_fail) | ||
|
|
||
| row = "| {:<30} | {:>8} | {:>8} | {:>8} | {:>8} |".format( | ||
| folder, total, empty, passed, failed | ||
| ) | ||
| print(row) | ||
|
|
||
| print(separator) | ||
|
|
||
| def main(): | ||
| parser = argparse.ArgumentParser( | ||
| description='Analyze maintenance charts in .rst files.', | ||
| formatter_class=argparse.RawDescriptionHelpFormatter, | ||
| epilog=""" | ||
| Examples: | ||
| %(prog)s # Run from current directory (depth=1) | ||
| %(prog)s path/to/docs # Run from specified directory | ||
| %(prog)s --depth 2 # Group by two directory levels | ||
| %(prog)s --depth 0 # Show full directory paths | ||
| %(prog)s -h # Show this help message | ||
| """ | ||
| ) | ||
|
|
||
| parser.add_argument( | ||
| 'start_dir', | ||
| nargs='?', | ||
| default='.', | ||
| help='Directory to start scanning from (default: current directory)' | ||
| ) | ||
|
|
||
| parser.add_argument( | ||
| '--depth', '-d', | ||
| type=int, | ||
| default=1, | ||
| help='Number of directory levels to show in report (0 for full path, default: 1)' | ||
| ) | ||
|
|
||
| args = parser.parse_args() | ||
|
|
||
| # Convert to absolute path and verify directory exists | ||
| start_path = os.path.abspath(args.start_dir) | ||
| if not os.path.isdir(start_path): | ||
| print(f"Error: Directory '{args.start_dir}' does not exist") | ||
| return | ||
|
|
||
| results = scan_directory(start_path, args.depth) | ||
| print_report(results) | ||
|
|
||
| if __name__ == "__main__": | ||
| main() | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Site Ops dir doesn't bold the maintenance chart title, and the Community pages have no maintenance charts at all. I'll fix this so we get a more accurate report.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#1009