diff --git a/.github/workflows/monthly-capacity-report.yml b/.github/workflows/monthly-capacity-report.yml index 52b59b4b0..ff140581f 100644 --- a/.github/workflows/monthly-capacity-report.yml +++ b/.github/workflows/monthly-capacity-report.yml @@ -13,39 +13,84 @@ permissions: id-token: write # Required for AWS OIDC authentication jobs: - generate-report: + export-dashboards: runs-on: ubuntu-latest - environment: prod + environment: reporting + strategy: + matrix: + env_config: + - name: Prod + dashboard: Demand_And_Capacity_Prod + account_secret: AWS_PROD_ACCOUNT_ID + - name: Preprod + dashboard: Demand_And_Capacity_Preprod + account_secret: AWS_PREPROD_ACCOUNT_ID + - name: Test + dashboard: Demand_And_Capacity_Test + account_secret: AWS_TEST_ACCOUNT_ID + - name: Dev + dashboard: Demand_And_Capacity_Dev + account_secret: AWS_DEV_ACCOUNT_ID steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version: "3.11" - - name: "Configure AWS Credentials" + - name: Configure AWS Credentials (${{ matrix.env_config.name }}) uses: aws-actions/configure-aws-credentials@v5 with: - role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/service-roles/github-actions-api-deployment-role + role-to-assume: arn:aws:iam::${{ secrets[matrix.env_config.account_secret] }}:role/service-roles/github-actions-api-deployment-role aws-region: eu-west-2 - - name: Generate dashboard report + - name: Export Dashboard (${{ matrix.env_config.name }}) run: | chmod +x scripts/export_dashboard_image.sh - ./scripts/export_dashboard_image.sh Demand_And_Capacity_Prod + ./scripts/export_dashboard_image.sh ${{ matrix.env_config.dashboard }} ${{ matrix.env_config.name }} env: AWS_REGION: eu-west-2 + - name: Upload dashboard export + uses: actions/upload-artifact@v4 + with: + name: dashboard-${{ matrix.env_config.name }} + path: dashboard_exports/**/* + + generate-report: + runs-on: ubuntu-latest + needs: export-dashboards + environment: reporting + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.11" + + - name: Download all dashboard exports + uses: actions/download-artifact@v4 + with: + path: dashboard_exports + pattern: dashboard-* + merge-multiple: true + + - name: Generate Combined Report + run: python3 scripts/generate_dashboard_report.py --input dashboard_exports + - name: Upload report as artifact uses: actions/upload-artifact@v5 with: name: capacity-report path: | - dashboard_exports/*.html - dashboard_exports/*.png + dashboard_exports/**/*.html + dashboard_exports/**/*.png retention-days: 90 - name: Send to Slack @@ -54,21 +99,20 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_D_AND_C_WEBHOOK }} run: | # Get the latest HTML report - REPORT_FILE=$(ls -t dashboard_exports/dashboard_report_*.html | head -1) + REPORT_FILE=$(find dashboard_exports -name "dashboard_report_*.html" | head -n 1) REPORT_NAME=$(basename "$REPORT_FILE") # GitHub Actions URL GITHUB_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" - # Send Slack notification with simple variables for Workflow Automation + # Send Slack notification curl -X POST "$SLACK_WEBHOOK_URL" \ -H 'Content-Type: application/json' \ -d @- <No data found for {env_name}

" - # Find all PNG images image_files = sorted(images_path.glob('*.png')) - if not image_files: - print(f"Error: No PNG images found in {images_dir}") - return None + return f"

No images found for {env_name}

" + + html = f""" +
+

{env_name}

+ """ + + for idx, image_file in enumerate(image_files, 1): + filename = image_file.stem + title = filename.split('_', 1)[1] if '_' in filename else filename + title = title.replace('_', ' ') + description = get_widget_description(title) + + with open(image_file, 'rb') as f: + image_data = base64.b64encode(f.read()).decode('utf-8') + + html += f""" +
+
+
{idx}. {title}
+
{description}
+
+
+ {title} +
+
+ """ - print(f"Found {len(image_files)} images to include in report") + html += "
" + return html + +def generate_html_report(base_dir='dashboard_exports', output_file=None): + """ + Generate an HTML report with all dashboard widget images from multiple environments. + """ + base_path = Path(base_dir) + + if not base_path.exists(): + return None # Default output filename if output_file is None: timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') - output_file = f'{images_dir}/dashboard_report_{timestamp}.html' + output_file = f'{base_dir}/dashboard_report_{timestamp}.html' - # Get dashboard name and timestamp from definition file dashboard_name = "Monthly Demand And Capacity Report - EliD" - report_date = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + report_date = datetime.now().strftime('%d %B %Y at %H:%M') # Build HTML - html_content = f""" + # Read CSS from file to embed in HTML + css_path = Path(__file__).parent / "dashboard_report.css" + try: + with open(css_path, "r", encoding="utf-8") as css_file: + css_content = css_file.read() + except Exception as e: + print(f"[ERROR] Could not read CSS file: {e}") + css_content = "" + + html_content = f''' - Dashboard Report - {dashboard_name} + NHS Dashboard Report - {dashboard_name} -
-
-

šŸ“Š {dashboard_name}

-
Last 8 Weeks Report • Generated: {report_date}
+
+
+
+ +

{dashboard_name}

+
Generated on {report_date}
+
+
+
+''' -
-""" - - # Add each widget image - for idx, image_file in enumerate(image_files, 1): - # Extract widget title from filename (remove number prefix and extension) - filename = image_file.stem - # Remove leading number and underscore (e.g., "01_") - title = filename.split('_', 1)[1] if '_' in filename else filename - # Replace underscores with spaces - title = title.replace('_', ' ') + # --------------------------------------------------------- + # Section 1: Production + # --------------------------------------------------------- + html_content += """ +
+

Production Environment

+
+ """ + html_content += generate_section_html("Prod", base_path / "Prod") - # Read and encode image - with open(image_file, 'rb') as f: - image_data = base64.b64encode(f.read()).decode('utf-8') + # --------------------------------------------------------- + # Section 2: Preprod Environments + # --------------------------------------------------------- + html_content += """ +
+

Preprod Environments

+
+ """ - html_content += f""" -
-
{idx}. {title}
- {title} -
-""" + # Order: Preprod, Test, Dev + for env in ["Preprod", "Test", "Dev"]: + html_content += generate_section_html(env, base_path / env) # Close HTML html_content += """ -
+
- + """ - # Write HTML file - with open(output_file, 'w', encoding='utf-8') as f: - f.write(html_content) + # Print output path for debugging + print(f"[DEBUG] Output HTML file will be written to: {output_file}") + # Ensure output directory exists + output_dir = Path(output_file).parent + if not output_dir.exists(): + print(f"[DEBUG] Creating output directory: {output_dir}") + output_dir.mkdir(parents=True, exist_ok=True) + + # Write HTML file with error handling + try: + with open(output_file, 'w', encoding='utf-8') as f: + f.write(html_content) + print(f"[DEBUG] Successfully wrote report to {output_file}") + except Exception as e: + print(f"[ERROR] Failed to write report: {e}") + + # --------------------------------------------------------- + # Section 1: Production + # --------------------------------------------------------- + html_content += """ +
+

Production Environment

+
+ """ + html_content += generate_section_html("Prod", base_path / "Prod") + + # --------------------------------------------------------- + # Section 2: Preprod Environments + # --------------------------------------------------------- + html_content += """ +
+

Preprod Environments

+
+ """ - print(f"\nāœ“ Report generated: {output_file}") - print(f"\nTo view:") - print(f" - Open in browser: file://{Path(output_file).absolute()}") - print(f" - Or run: xdg-open {output_file}") - print(f"\nTo save as PDF: Open in browser → Print → Save as PDF") + # Order: Preprod, Test, Dev + for env in ["Preprod", "Test", "Dev"]: + html_content += generate_section_html(env, base_path / env) - return output_file + # Close HTML + html_content += """ +
+ + + + +""" +# Only run this block if the script is executed directly if __name__ == "__main__": import argparse