Skip to main content
Glama
Raistlin82

SAP OData to MCP Server

by Raistlin82

UI Report Builder

ui-report-builder

Generate drill-down reports with analytical capabilities from SAP OData services. Create summary, detailed, analytical, or custom reports by specifying entity types, dimensions, and measures for data analysis.

Instructions

Creates comprehensive drill-down reports with analytical capabilities

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
entityTypeYesSAP entity type for the report
reportTypeYesType of report to generate
dimensionsYesReport dimension fields
measuresYesReport measure fields

Implementation Reference

  • The UIReportBuilderTool class is the main handler for the 'ui-report-builder' tool. It implements MCPToolHandler, defines the tool name and description, and contains the handle() method that performs authentication validation, builds report configuration, and generates a comprehensive HTML-based report interface with charts, tables, drill-down, export, and scheduling features.
    export class UIReportBuilderTool implements MCPToolHandler {
        private logger = new Logger('UiReportBuilderTool');
    
      name = 'ui-report-builder';
      description = 'Creates comprehensive drill-down reports with analytical capabilities and export options';
      inputSchema = InputSchema;
    
      constructor(
        private renderingEngine: UIRenderingEngine,
        private componentLibrary: UIComponentLibrary,
        private entityManager: SAPEntityManager,
        private authValidator: AuthenticationValidator
      ) {}
    
      async handle(args: z.infer<typeof InputSchema>, context?: any): Promise<any> {
        try {
          // Validate authentication and authorization
          const authResult = await this.authValidator.validateToken(context?.token);
          if (!authResult.isValid) {
            throw new Error('Authentication required for report builder');
          }
    
          if (!authResult.scopes?.includes('ui.reports')) {
            throw new Error('Insufficient permissions for report builder (ui.reports scope required)');
          }
    
          // Get entity metadata
          const entityMetadata = await this.entityManager.getEntityMetadata(args.entityType);
          if (!entityMetadata) {
            throw new Error(`Entity type ${args.entityType} not found`);
          }
    
          // Build report configuration
          const reportConfig: ReportConfig = {
            entityType: args.entityType,
            reportType: args.reportType,
            dimensions: args.dimensions,
            measures: args.measures,
            filters: args.filters || [],
            drillDownLevels: this.buildDrillDownLevels(args.drillDownLevels || []),
            exportFormats: args.exportFormats || ['pdf', 'excel', 'csv'],
            schedulingEnabled: args.schedulingOptions?.enabled || false,
            visualizations: this.buildReportCharts(args.visualizations || [])
          };
    
          // Generate the report UI
          const reportHTML = await this.generateReportInterface(reportConfig, entityMetadata);
    
          return {
            content: [
              {
                type: 'text',
                text: `Report builder created for ${args.entityType} with ${args.reportType} configuration. Features drill-down capabilities, multiple export formats, and analytical visualizations.`
              },
              {
                type: 'text',
                text: reportHTML
              }
            ]
          };
    
        } catch (error) {
          return {
            content: [
              {
                type: 'text',
                text: `Error creating report builder: ${error instanceof Error ? error.message : 'Unknown error'}`
              }
            ]
          };
        }
      }
    
      private buildDrillDownLevels(levels: any[]): DrillDownLevel[] {
        return levels.map(level => ({
          field: level.field,
          targetEntity: level.entity,
          navigationProperty: level.navigationProperty,
          enabled: true
        }));
      }
    
      private buildReportCharts(visualizations: any[]): ReportChart[] {
        return visualizations.map(viz => ({
          type: viz.type,
          title: viz.title,
          xAxis: viz.xAxis,
          yAxis: viz.yAxis,
          groupBy: viz.groupBy,
          config: {
            responsive: true,
            maintainAspectRatio: false
          }
        }));
      }
    
      private async generateReportInterface(config: ReportConfig, entityMetadata: any): Promise<string> {
        const reportId = `report_${Date.now()}`;
    
        return `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>${config.entityType} Report Builder</title>
        <link rel="stylesheet" href="https://sdk.openui5.org/resources/sap/ui/core/themes/sap_horizon/library.css">
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
        <style>
            body {
                font-family: "72", "72full", Arial, Helvetica, sans-serif;
                margin: 0;
                padding: 20px;
                background-color: #f7f7f7;
            }
    
            .report-container {
                max-width: 1400px;
                margin: 0 auto;
                background: white;
                border-radius: 8px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.1);
                overflow: hidden;
            }
    
            .report-header {
                background: linear-gradient(135deg, #0070f3, #0051cc);
                color: white;
                padding: 24px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
    
            .report-title {
                font-size: 24px;
                font-weight: 600;
                margin: 0;
            }
    
            .report-actions {
                display: flex;
                gap: 12px;
            }
    
            .sap-button {
                background: rgba(255,255,255,0.2);
                color: white;
                border: 1px solid rgba(255,255,255,0.3);
                padding: 8px 16px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
                transition: all 0.2s;
            }
    
            .sap-button:hover {
                background: rgba(255,255,255,0.3);
                transform: translateY(-1px);
            }
    
            .sap-button.primary {
                background: #ff6b35;
                border-color: #ff6b35;
            }
    
            .sap-button.primary:hover {
                background: #e55a2b;
            }
    
            .report-toolbar {
                padding: 16px 24px;
                background: #f9f9f9;
                border-bottom: 1px solid #e6e6e6;
                display: flex;
                flex-wrap: wrap;
                gap: 16px;
                align-items: center;
            }
    
            .filter-group {
                display: flex;
                align-items: center;
                gap: 8px;
            }
    
            .filter-input {
                padding: 6px 12px;
                border: 1px solid #ccc;
                border-radius: 4px;
                font-size: 14px;
            }
    
            .report-content {
                padding: 24px;
            }
    
            .report-tabs {
                display: flex;
                border-bottom: 2px solid #e6e6e6;
                margin-bottom: 24px;
            }
    
            .report-tab {
                padding: 12px 20px;
                cursor: pointer;
                border-bottom: 3px solid transparent;
                font-weight: 500;
                transition: all 0.2s;
            }
    
            .report-tab.active {
                border-bottom-color: #0070f3;
                color: #0070f3;
            }
    
            .report-tab:hover {
                background: #f5f5f5;
            }
    
            .tab-content {
                display: none;
            }
    
            .tab-content.active {
                display: block;
            }
    
            .data-table {
                width: 100%;
                border-collapse: collapse;
                margin-bottom: 20px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }
    
            .data-table th,
            .data-table td {
                padding: 12px;
                text-align: left;
                border-bottom: 1px solid #e6e6e6;
            }
    
            .data-table th {
                background: #f8f9fa;
                font-weight: 600;
                position: sticky;
                top: 0;
                z-index: 10;
                cursor: pointer;
            }
    
            .data-table th:hover {
                background: #e9ecef;
            }
    
            .data-table tr:hover {
                background: #f8f9fa;
            }
    
            .drill-down-link {
                color: #0070f3;
                cursor: pointer;
                text-decoration: underline;
            }
    
            .drill-down-link:hover {
                color: #0051cc;
            }
    
            .chart-container {
                background: white;
                border-radius: 8px;
                padding: 20px;
                margin-bottom: 20px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }
    
            .chart-title {
                font-size: 18px;
                font-weight: 600;
                margin-bottom: 16px;
                color: #333;
            }
    
            .metrics-grid {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
                gap: 16px;
                margin-bottom: 24px;
            }
    
            .metric-card {
                background: white;
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
                text-align: center;
            }
    
            .metric-value {
                font-size: 32px;
                font-weight: 700;
                color: #0070f3;
                margin-bottom: 8px;
            }
    
            .metric-label {
                font-size: 14px;
                color: #666;
                font-weight: 500;
            }
    
            .breadcrumb {
                background: #f0f0f0;
                padding: 12px 16px;
                border-radius: 4px;
                margin-bottom: 16px;
                font-size: 14px;
            }
    
            .breadcrumb-item {
                color: #0070f3;
                cursor: pointer;
                text-decoration: underline;
            }
    
            .breadcrumb-separator {
                margin: 0 8px;
                color: #999;
            }
    
            .export-modal {
                display: none;
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0,0,0,0.5);
                z-index: 1000;
            }
    
            .export-dialog {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: white;
                padding: 24px;
                border-radius: 8px;
                min-width: 400px;
            }
    
            .export-options {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
                gap: 12px;
                margin: 16px 0;
            }
    
            .export-option {
                padding: 12px;
                border: 2px solid #e6e6e6;
                border-radius: 4px;
                text-align: center;
                cursor: pointer;
                transition: all 0.2s;
            }
    
            .export-option:hover {
                border-color: #0070f3;
                background: #f0f8ff;
            }
    
            .export-option.selected {
                border-color: #0070f3;
                background: #0070f3;
                color: white;
            }
    
            .loading {
                display: inline-block;
                width: 20px;
                height: 20px;
                border: 3px solid #f3f3f3;
                border-top: 3px solid #0070f3;
                border-radius: 50%;
                animation: spin 1s linear infinite;
            }
    
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
    
            .pagination {
                display: flex;
                justify-content: center;
                align-items: center;
                gap: 8px;
                margin-top: 20px;
            }
    
            .pagination button {
                padding: 8px 12px;
                border: 1px solid #ccc;
                background: white;
                cursor: pointer;
                border-radius: 4px;
            }
    
            .pagination button:hover {
                background: #f5f5f5;
            }
    
            .pagination button.active {
                background: #0070f3;
                color: white;
                border-color: #0070f3;
            }
    
            .schedule-section {
                background: #f8f9fa;
                padding: 20px;
                border-radius: 8px;
                margin-bottom: 20px;
            }
    
            .form-group {
                margin-bottom: 16px;
            }
    
            .form-label {
                display: block;
                margin-bottom: 4px;
                font-weight: 500;
                color: #333;
            }
    
            .form-control {
                width: 100%;
                padding: 8px 12px;
                border: 1px solid #ccc;
                border-radius: 4px;
                font-size: 14px;
            }
    
            .form-control:focus {
                outline: none;
                border-color: #0070f3;
                box-shadow: 0 0 0 2px rgba(0,112,243,0.2);
            }
    
            @media (max-width: 768px) {
                .report-toolbar {
                    flex-direction: column;
                    align-items: stretch;
                }
    
                .report-actions {
                    flex-direction: column;
                }
    
                .metrics-grid {
                    grid-template-columns: 1fr;
                }
            }
        </style>
    </head>
    <body>
        <div class="report-container">
            <div class="report-header">
                <h1 class="report-title">${config.entityType} ${config.reportType.charAt(0).toUpperCase() + config.reportType.slice(1)} Report</h1>
                <div class="report-actions">
                    <button class="sap-button" onclick="refreshReport()">
                        <span id="refresh-icon">🔄</span> Refresh
                    </button>
                    <button class="sap-button" onclick="showExportModal()">📤 Export</button>
                    <button class="sap-button primary" onclick="showScheduleModal()">⏰ Schedule</button>
                </div>
            </div>
    
            <div class="report-toolbar">
                <div class="filter-group">
                    <label>Date Range:</label>
                    <input type="date" class="filter-input" id="startDate">
                    <span>to</span>
                    <input type="date" class="filter-input" id="endDate">
                </div>
    
                <div class="filter-group">
                    <label>Quick Filter:</label>
                    <input type="text" class="filter-input" id="quickFilter" placeholder="Search...">
                </div>
    
                <div class="filter-group">
                    <label>Group By:</label>
                    <select class="filter-input" id="groupBy">
                        <option value="">None</option>
                        ${config.dimensions.map(dim => `<option value="${dim}">${dim}</option>`).join('')}
                    </select>
                </div>
    
                <button class="sap-button" onclick="applyFilters()">Apply Filters</button>
                <button class="sap-button" onclick="clearFilters()">Clear</button>
            </div>
    
            <div class="report-content">
                <div class="breadcrumb" id="breadcrumb" style="display: none;">
                    <span class="breadcrumb-item" onclick="navigateToLevel(0)">Summary</span>
                </div>
    
                <div class="report-tabs">
                    <div class="report-tab active" onclick="switchTab('summary')">Summary</div>
                    <div class="report-tab" onclick="switchTab('data')">Data</div>
                    <div class="report-tab" onclick="switchTab('charts')">Charts</div>
                    <div class="report-tab" onclick="switchTab('analytics')">Analytics</div>
                </div>
    
                <div id="summary-tab" class="tab-content active">
                    <div class="metrics-grid">
                        ${config.measures.map(measure => `
                            <div class="metric-card">
                                <div class="metric-value" id="metric-${measure}">-</div>
                                <div class="metric-label">${measure}</div>
                            </div>
                        `).join('')}
                    </div>
    
                    <div class="chart-container">
                        <div class="chart-title">Trend Analysis</div>
                        <canvas id="trendChart" width="400" height="200"></canvas>
                    </div>
                </div>
    
                <div id="data-tab" class="tab-content">
                    <div style="overflow-x: auto;">
                        <table class="data-table" id="dataTable">
                            <thead>
                                <tr>
                                    ${[...config.dimensions, ...config.measures].map(field => `
                                        <th onclick="sortTable('${field}')">${field} ↕️</th>
                                    `).join('')}
                                    ${(config.drillDownLevels?.length || 0) > 0 ? '<th>Actions</th>' : ''}
                                </tr>
                            </thead>
                            <tbody id="dataTableBody">
                                <tr><td colspan="${[...config.dimensions, ...config.measures].length + ((config.drillDownLevels?.length || 0) > 0 ? 1 : 0)}">Loading data...</td></tr>
                            </tbody>
                        </table>
                    </div>
    
                    <div class="pagination" id="pagination"></div>
                </div>
    
                <div id="charts-tab" class="tab-content">
                    ${(config.visualizations || []).map((chart, index) => `
                        <div class="chart-container">
                            <div class="chart-title">${chart.title}</div>
                            <canvas id="chart-${index}" width="400" height="300"></canvas>
                        </div>
                    `).join('')}
                </div>
    
                <div id="analytics-tab" class="tab-content">
                    <div class="chart-container">
                        <div class="chart-title">Advanced Analytics</div>
                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
                            <canvas id="correlationChart" width="300" height="300"></canvas>
                            <canvas id="distributionChart" width="300" height="300"></canvas>
                        </div>
                    </div>
    
                    <div class="schedule-section">
                        <h3>Statistical Summary</h3>
                        <div id="statisticalSummary">Loading statistical analysis...</div>
                    </div>
                </div>
            </div>
        </div>
    
        <!-- Export Modal -->
        <div id="exportModal" class="export-modal">
            <div class="export-dialog">
                <h3>Export Report</h3>
                <div class="export-options">
                    ${(config.exportFormats || []).map(format => `
                        <div class="export-option" onclick="selectExportFormat('${format}')">
                            ${format.toUpperCase()}
                        </div>
                    `).join('')}
                </div>
                <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
                    <button class="sap-button" onclick="hideExportModal()">Cancel</button>
                    <button class="sap-button primary" onclick="exportReport()">Export</button>
                </div>
            </div>
        </div>
    
        <!-- Schedule Modal -->
        <div id="scheduleModal" class="export-modal">
            <div class="export-dialog">
                <h3>Schedule Report</h3>
                <div class="form-group">
                    <label class="form-label">Frequency:</label>
                    <select class="form-control" id="scheduleFrequency">
                        <option value="daily">Daily</option>
                        <option value="weekly">Weekly</option>
                        <option value="monthly">Monthly</option>
                        <option value="quarterly">Quarterly</option>
                    </select>
                </div>
                <div class="form-group">
                    <label class="form-label">Recipients (comma-separated emails):</label>
                    <input type="text" class="form-control" id="scheduleRecipients" placeholder="user1@company.com, user2@company.com">
                </div>
                <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
                    <button class="sap-button" onclick="hideScheduleModal()">Cancel</button>
                    <button class="sap-button primary" onclick="scheduleReport()">Schedule</button>
                </div>
            </div>
        </div>
    
        <script>
            // Report state management
            let currentData = [];
            let filteredData = [];
            let currentPage = 1;
            let pageSize = 50;
            let drillDownStack = [];
            let currentLevel = 0;
            let sortColumn = '';
            let sortDirection = 'asc';
            let selectedExportFormat = 'pdf';
    
            // Configuration
            const reportConfig = ${JSON.stringify(config)};
    
            // Initialize report
            document.addEventListener('DOMContentLoaded', function() {
                loadInitialData();
                initializeCharts();
                setDefaultDateRange();
            });
    
            function setDefaultDateRange() {
                const endDate = new Date();
                const startDate = new Date();
                startDate.setMonth(startDate.getMonth() - 1);
    
                document.getElementById('startDate').value = startDate.toISOString().split('T')[0];
                document.getElementById('endDate').value = endDate.toISOString().split('T')[0];
            }
    
            async function loadInitialData() {
                try {
                    showLoading('dataTableBody');
    
                    // Simulate data loading
                    const mockData = generateMockData(100);
                    currentData = mockData;
                    filteredData = [...currentData];
    
                    updateSummaryMetrics();
                    updateDataTable();
                    updateCharts();
                    updateAnalytics();
    
                } catch (error) {
                    this.logger.error('Error loading data:', { error: error });
                    showError('Failed to load report data');
                }
            }
    
            function generateMockData(count) {
                const data = [];
                const statuses = ['Active', 'Inactive', 'Pending', 'Completed'];
                const categories = ['A', 'B', 'C', 'D'];
    
                for (let i = 0; i < count; i++) {
                    const record = {};
    
                    reportConfig.dimensions.forEach(dim => {
                        switch (dim.toLowerCase()) {
                            case 'status':
                                record[dim] = statuses[Math.floor(Math.random() * statuses.length)];
                                break;
                            case 'category':
                                record[dim] = categories[Math.floor(Math.random() * categories.length)];
                                break;
                            case 'date':
                                const date = new Date();
                                date.setDate(date.getDate() - Math.floor(Math.random() * 365));
                                record[dim] = date.toISOString().split('T')[0];
                                break;
                            default:
                                record[dim] = \`\${dim}_\${i + 1}\`;
                        }
                    });
    
                    reportConfig.measures.forEach(measure => {
                        record[measure] = Math.floor(Math.random() * 10000) + 1000;
                    });
    
                    record.id = i + 1;
                    data.push(record);
                }
    
                return data;
            }
    
            function updateSummaryMetrics() {
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const sum = values.reduce((a, b) => a + b, 0);
                    const avg = sum / values.length;
                    const max = Math.max(...values);
    
                    const element = document.getElementById(\`metric-\${measure}\`);
                    if (element) {
                        element.textContent = formatNumber(sum);
                        element.title = \`Average: \${formatNumber(avg)}, Max: \${formatNumber(max)}\`;
                    }
                });
            }
    
            function updateDataTable() {
                const tbody = document.getElementById('dataTableBody');
                const startIndex = (currentPage - 1) * pageSize;
                const endIndex = startIndex + pageSize;
                const pageData = filteredData.slice(startIndex, endIndex);
    
                tbody.innerHTML = pageData.map(row => \`
                    <tr>
                        \${[...reportConfig.dimensions, ...reportConfig.measures].map(field => \`
                            <td>\${formatCellValue(row[field], field)}</td>
                        \`).join('')}
                        \${reportConfig.drillDownLevels.length > 0 ? \`
                            <td>
                                <span class="drill-down-link" onclick="drillDown('\${row.id}', '\${row[reportConfig.dimensions[0]]}')">
                                    Drill Down
                                </span>
                            </td>
                        \` : ''}
                    </tr>
                \`).join('');
    
                updatePagination();
            }
    
            function formatCellValue(value, field) {
                if (value === null || value === undefined) return '-';
    
                if (reportConfig.measures.includes(field)) {
                    return formatNumber(value);
                }
    
                if (field.toLowerCase().includes('date')) {
                    return new Date(value).toLocaleDateString();
                }
    
                return value;
            }
    
            function formatNumber(num) {
                if (num >= 1000000) {
                    return (num / 1000000).toFixed(1) + 'M';
                } else if (num >= 1000) {
                    return (num / 1000).toFixed(1) + 'K';
                }
                return num.toLocaleString();
            }
    
            function updatePagination() {
                const totalPages = Math.ceil(filteredData.length / pageSize);
                const pagination = document.getElementById('pagination');
    
                let paginationHTML = '';
    
                // Previous button
                if (currentPage > 1) {
                    paginationHTML += \`<button onclick="changePage(\${currentPage - 1})">← Previous</button>\`;
                }
    
                // Page numbers
                const startPage = Math.max(1, currentPage - 2);
                const endPage = Math.min(totalPages, currentPage + 2);
    
                for (let i = startPage; i <= endPage; i++) {
                    const activeClass = i === currentPage ? 'active' : '';
                    paginationHTML += \`<button class="\${activeClass}" onclick="changePage(\${i})">\${i}</button>\`;
                }
    
                // Next button
                if (currentPage < totalPages) {
                    paginationHTML += \`<button onclick="changePage(\${currentPage + 1})">Next →</button>\`;
                }
    
                pagination.innerHTML = paginationHTML;
            }
    
            function changePage(page) {
                currentPage = page;
                updateDataTable();
            }
    
            function initializeCharts() {
                // Trend chart
                const trendCtx = document.getElementById('trendChart').getContext('2d');
                new Chart(trendCtx, {
                    type: 'line',
                    data: {
                        labels: getLast12Months(),
                        datasets: reportConfig.measures.map((measure, index) => ({
                            label: measure,
                            data: generateTrendData(),
                            borderColor: getChartColor(index),
                            backgroundColor: getChartColor(index, 0.1),
                            tension: 0.4
                        }))
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Monthly Trend'
                            }
                        }
                    }
                });
    
                // Initialize visualization charts
                reportConfig.visualizations.forEach((chart, index) => {
                    initializeVisualizationChart(chart, index);
                });
    
                // Analytics charts
                initializeAnalyticsCharts();
            }
    
            function initializeVisualizationChart(chartConfig, index) {
                const ctx = document.getElementById(\`chart-\${index}\`).getContext('2d');
    
                new Chart(ctx, {
                    type: chartConfig.type,
                    data: generateChartData(chartConfig),
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            title: {
                                display: true,
                                text: chartConfig.title
                            }
                        }
                    }
                });
            }
    
            function initializeAnalyticsCharts() {
                // Correlation chart
                const correlationCtx = document.getElementById('correlationChart').getContext('2d');
                new Chart(correlationCtx, {
                    type: 'scatter',
                    data: {
                        datasets: [{
                            label: 'Correlation Analysis',
                            data: generateCorrelationData(),
                            backgroundColor: 'rgba(0, 112, 243, 0.6)'
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Measure Correlation'
                            }
                        }
                    }
                });
    
                // Distribution chart
                const distributionCtx = document.getElementById('distributionChart').getContext('2d');
                new Chart(distributionCtx, {
                    type: 'bar',
                    data: {
                        labels: ['Q1', 'Q2', 'Q3', 'Q4'],
                        datasets: [{
                            label: 'Distribution',
                            data: [25, 35, 28, 42],
                            backgroundColor: [
                                'rgba(255, 107, 53, 0.8)',
                                'rgba(0, 112, 243, 0.8)',
                                'rgba(40, 167, 69, 0.8)',
                                'rgba(255, 193, 7, 0.8)'
                            ]
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Quartile Distribution'
                            }
                        }
                    }
                });
            }
    
            function generateChartData(chartConfig) {
                const labels = getUniqueValues(filteredData, chartConfig.xAxis);
                const data = labels.map(label => {
                    const filtered = filteredData.filter(row => row[chartConfig.xAxis] === label);
                    return filtered.reduce((sum, row) => sum + (row[chartConfig.yAxis] || 0), 0);
                });
    
                return {
                    labels: labels,
                    datasets: [{
                        label: chartConfig.yAxis,
                        data: data,
                        backgroundColor: chartConfig.type === 'pie' ?
                            labels.map((_, i) => getChartColor(i, 0.8)) :
                            getChartColor(0, 0.8),
                        borderColor: getChartColor(0),
                        borderWidth: 1
                    }]
                };
            }
    
            function getUniqueValues(data, field) {
                return [...new Set(data.map(row => row[field]))].slice(0, 10);
            }
    
            function generateTrendData() {
                return Array.from({length: 12}, () => Math.floor(Math.random() * 1000) + 500);
            }
    
            function generateCorrelationData() {
                return Array.from({length: 50}, () => ({
                    x: Math.random() * 100,
                    y: Math.random() * 100
                }));
            }
    
            function getLast12Months() {
                const months = [];
                const now = new Date();
                for (let i = 11; i >= 0; i--) {
                    const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
                    months.push(date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }));
                }
                return months;
            }
    
            function getChartColor(index, alpha = 1) {
                const colors = [
                    \`rgba(0, 112, 243, \${alpha})\`,
                    \`rgba(255, 107, 53, \${alpha})\`,
                    \`rgba(40, 167, 69, \${alpha})\`,
                    \`rgba(255, 193, 7, \${alpha})\`,
                    \`rgba(108, 117, 125, \${alpha})\`
                ];
                return colors[index % colors.length];
            }
    
            function updateCharts() {
                // Update existing charts with filtered data
                updateSummaryMetrics();
            }
    
            function updateAnalytics() {
                const summary = document.getElementById('statisticalSummary');
    
                let analyticsHTML = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">';
    
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const mean = values.reduce((a, b) => a + b, 0) / values.length;
                    const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
                    const stdDev = Math.sqrt(variance);
    
                    analyticsHTML += \`
                        <div class="metric-card">
                            <h4>\${measure}</h4>
                            <p><strong>Mean:</strong> \${formatNumber(mean)}</p>
                            <p><strong>Std Dev:</strong> \${formatNumber(stdDev)}</p>
                            <p><strong>Min:</strong> \${formatNumber(Math.min(...values))}</p>
                            <p><strong>Max:</strong> \${formatNumber(Math.max(...values))}</p>
                        </div>
                    \`;
                });
    
                analyticsHTML += '</div>';
                summary.innerHTML = analyticsHTML;
            }
    
            // Event handlers
            function switchTab(tabName) {
                // Hide all tabs
                document.querySelectorAll('.tab-content').forEach(tab => {
                    tab.classList.remove('active');
                });
                document.querySelectorAll('.report-tab').forEach(tab => {
                    tab.classList.remove('active');
                });
    
                // Show selected tab
                document.getElementById(\`\${tabName}-tab\`).classList.add('active');
                event.target.classList.add('active');
            }
    
            function applyFilters() {
                const startDate = document.getElementById('startDate').value;
                const endDate = document.getElementById('endDate').value;
                const quickFilter = document.getElementById('quickFilter').value.toLowerCase();
                const groupBy = document.getElementById('groupBy').value;
    
                filteredData = currentData.filter(row => {
                    let matchesFilter = true;
    
                    // Date filter
                    if (startDate && endDate && row.date) {
                        const rowDate = new Date(row.date);
                        matchesFilter = matchesFilter && rowDate >= new Date(startDate) && rowDate <= new Date(endDate);
                    }
    
                    // Quick filter
                    if (quickFilter) {
                        const rowText = Object.values(row).join(' ').toLowerCase();
                        matchesFilter = matchesFilter && rowText.includes(quickFilter);
                    }
    
                    return matchesFilter;
                });
    
                // Group by functionality
                if (groupBy) {
                    // Implement grouping logic here
                    this.logger.debug('Grouping by:', groupBy);
                }
    
                currentPage = 1;
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
                updateAnalytics();
            }
    
            function clearFilters() {
                document.getElementById('startDate').value = '';
                document.getElementById('endDate').value = '';
                document.getElementById('quickFilter').value = '';
                document.getElementById('groupBy').value = '';
    
                filteredData = [...currentData];
                currentPage = 1;
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
                updateAnalytics();
            }
    
            function sortTable(column) {
                if (sortColumn === column) {
                    sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
                } else {
                    sortColumn = column;
                    sortDirection = 'asc';
                }
    
                filteredData.sort((a, b) => {
                    let aVal = a[column];
                    let bVal = b[column];
    
                    if (typeof aVal === 'string') {
                        aVal = aVal.toLowerCase();
                        bVal = bVal.toLowerCase();
                    }
    
                    if (sortDirection === 'asc') {
                        return aVal > bVal ? 1 : -1;
                    } else {
                        return aVal < bVal ? 1 : -1;
                    }
                });
    
                updateDataTable();
            }
    
            function drillDown(id, value) {
                drillDownStack.push({
                    level: currentLevel,
                    data: [...filteredData],
                    title: \`\${reportConfig.entityType} Report\`
                });
    
                currentLevel++;
    
                // Filter data for drill-down
                filteredData = currentData.filter(row => row[reportConfig.dimensions[0]] === value);
    
                // Update breadcrumb
                updateBreadcrumb(value);
    
                // Update displays
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
            }
    
            function navigateToLevel(level) {
                if (level < currentLevel && level >= 0) {
                    const targetState = drillDownStack[level];
                    if (targetState) {
                        filteredData = targetState.data;
                        currentLevel = level;
                        drillDownStack = drillDownStack.slice(0, level);
    
                        updateBreadcrumb();
                        updateDataTable();
                        updateSummaryMetrics();
                        updateCharts();
                    }
                }
            }
    
            function updateBreadcrumb(currentItem) {
                const breadcrumb = document.getElementById('breadcrumb');
    
                if (currentLevel === 0) {
                    breadcrumb.style.display = 'none';
                    return;
                }
    
                breadcrumb.style.display = 'block';
                let breadcrumbHTML = '<span class="breadcrumb-item" onclick="navigateToLevel(0)">Summary</span>';
    
                drillDownStack.forEach((item, index) => {
                    breadcrumbHTML += \`<span class="breadcrumb-separator">›</span><span class="breadcrumb-item" onclick="navigateToLevel(\${index + 1})">\${item.title}</span>\`;
                });
    
                if (currentItem) {
                    breadcrumbHTML += \`<span class="breadcrumb-separator">›</span><span>\${currentItem}</span>\`;
                }
    
                breadcrumb.innerHTML = breadcrumbHTML;
            }
    
            async function refreshReport() {
                const icon = document.getElementById('refresh-icon');
                icon.innerHTML = '<div class="loading"></div>';
    
                try {
                    await loadInitialData();
                    showMessage('Report refreshed successfully', 'success');
                } catch (error) {
                    showMessage('Failed to refresh report', 'error');
                } finally {
                    icon.innerHTML = '🔄';
                }
            }
    
            function showExportModal() {
                document.getElementById('exportModal').style.display = 'block';
            }
    
            function hideExportModal() {
                document.getElementById('exportModal').style.display = 'none';
            }
    
            function selectExportFormat(format) {
                document.querySelectorAll('.export-option').forEach(option => {
                    option.classList.remove('selected');
                });
                event.target.classList.add('selected');
                selectedExportFormat = format;
            }
    
            async function exportReport() {
                try {
                    switch (selectedExportFormat) {
                        case 'pdf':
                            await exportToPDF();
                            break;
                        case 'excel':
                            await exportToExcel();
                            break;
                        case 'csv':
                            await exportToCSV();
                            break;
                        case 'json':
                            await exportToJSON();
                            break;
                    }
    
                    showMessage(\`Report exported as \${selectedExportFormat.toUpperCase()}\`, 'success');
                    hideExportModal();
                } catch (error) {
                    showMessage('Export failed', 'error');
                }
            }
    
            async function exportToPDF() {
                const { jsPDF } = window.jspdf;
                const doc = new jsPDF();
    
                doc.setFontSize(20);
                doc.text(\`\${reportConfig.entityType} Report\`, 20, 30);
    
                doc.setFontSize(12);
                doc.text(\`Generated: \${new Date().toLocaleDateString()}\`, 20, 45);
                doc.text(\`Records: \${filteredData.length}\`, 20, 55);
    
                // Add summary metrics
                let yPos = 75;
                doc.setFontSize(16);
                doc.text('Summary Metrics', 20, yPos);
                yPos += 15;
    
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const sum = values.reduce((a, b) => a + b, 0);
                    doc.setFontSize(12);
                    doc.text(\`\${measure}: \${formatNumber(sum)}\`, 20, yPos);
                    yPos += 10;
                });
    
                doc.save(\`\${reportConfig.entityType}_report.pdf\`);
            }
    
            async function exportToExcel() {
                const ws = XLSX.utils.json_to_sheet(filteredData);
                const wb = XLSX.utils.book_new();
                XLSX.utils.book_append_sheet(wb, ws, 'Report Data');
    
                XLSX.writeFile(wb, \`\${reportConfig.entityType}_report.xlsx\`);
            }
    
            async function exportToCSV() {
                const headers = [...reportConfig.dimensions, ...reportConfig.measures];
                const csvContent = [
                    headers.join(','),
                    ...filteredData.map(row => headers.map(header => row[header] || '').join(','))
                ].join('\\n');
    
                const blob = new Blob([csvContent], { type: 'text/csv' });
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = \`\${reportConfig.entityType}_report.csv\`;
                a.click();
                window.URL.revokeObjectURL(url);
            }
    
            async function exportToJSON() {
                const jsonContent = JSON.stringify(filteredData, null, 2);
                const blob = new Blob([jsonContent], { type: 'application/json' });
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = \`\${reportConfig.entityType}_report.json\`;
                a.click();
                window.URL.revokeObjectURL(url);
            }
    
            function showScheduleModal() {
                document.getElementById('scheduleModal').style.display = 'block';
            }
    
            function hideScheduleModal() {
                document.getElementById('scheduleModal').style.display = 'none';
            }
    
            async function scheduleReport() {
                const frequency = document.getElementById('scheduleFrequency').value;
                const recipients = document.getElementById('scheduleRecipients').value;
    
                if (!recipients.trim()) {
                    showMessage('Please enter at least one recipient email', 'error');
                    return;
                }
    
                try {
                    // Here you would call your scheduling API
                    this.logger.debug('Scheduling report:', { frequency, recipients });
                    showMessage(\`Report scheduled \${frequency} for \${recipients}\`, 'success');
                    hideScheduleModal();
                } catch (error) {
                    showMessage('Failed to schedule report', 'error');
                }
            }
    
            function showMessage(message, type) {
                // Create toast notification
                const toast = document.createElement('div');
                toast.style.cssText = \`
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    padding: 12px 20px;
                    border-radius: 4px;
                    color: white;
                    font-weight: 500;
                    z-index: 2000;
                    background: \${type === 'success' ? '#28a745' : '#dc3545'};
                    animation: slideIn 0.3s ease;
                \`;
                toast.textContent = message;
    
                document.body.appendChild(toast);
    
                setTimeout(() => {
                    toast.remove();
                }, 3000);
            }
    
            function showLoading(elementId) {
                const element = document.getElementById(elementId);
                if (element) {
                    element.innerHTML = '<div style="text-align: center; padding: 20px;"><div class="loading"></div></div>';
                }
            }
    
            function showError(message) {
                const element = document.getElementById('dataTableBody');
                if (element) {
                    element.innerHTML = \`<tr><td colspan="10" style="text-align: center; color: red; padding: 20px;">\${message}</td></tr>\`;
                }
            }
    
            // Add CSS animation
            const style = document.createElement('style');
            style.textContent = \`
                @keyframes slideIn {
                    from {
                        transform: translateX(100%);
                        opacity: 0;
                    }
                    to {
                        transform: translateX(0);
                        opacity: 1;
                    }
                }
            \`;
            document.head.appendChild(style);
        </script>
    </body>
    </html>`;
      }
    }
  • Zod schema defining the input parameters for the ui-report-builder tool, including entity type, report type, dimensions, measures, filters, drill-down levels, export formats, scheduling options, and visualizations.
    const InputSchema = z.object({
      entityType: z.string().describe('SAP entity type for the report (e.g., "Customer", "SalesOrder")'),
      reportType: z.enum(['summary', 'detailed', 'analytical', 'custom']).describe('Type of report to generate'),
      dimensions: z.array(z.string()).describe('Fields to use as report dimensions'),
      measures: z.array(z.string()).describe('Fields to use as report measures'),
      filters: z.array(z.object({
        field: z.string(),
        operator: z.enum(['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'contains', 'startswith', 'endswith']),
        value: z.union([z.string(), z.number(), z.boolean()])
      })).optional().describe('Optional filters to apply to the report'),
      drillDownLevels: z.array(z.object({
        field: z.string(),
        entity: z.string().optional(),
        navigationProperty: z.string().optional()
      })).optional().describe('Drill-down levels configuration'),
      exportFormats: z.array(z.enum(['pdf', 'excel', 'csv', 'json', 'xml'])).optional().describe('Export formats to enable'),
      schedulingOptions: z.object({
        enabled: z.boolean(),
        frequency: z.enum(['daily', 'weekly', 'monthly', 'quarterly']).optional(),
        recipients: z.array(z.string()).optional()
      }).optional().describe('Report scheduling configuration'),
      visualizations: z.array(z.object({
        type: z.enum(['bar', 'line', 'pie', 'scatter', 'area', 'table']),
        title: z.string(),
        xAxis: z.string(),
        yAxis: z.string(),
        groupBy: z.string().optional()
      })).optional().describe('Chart visualizations for the report')
    });
  • Core helper method that generates the full interactive HTML UI for the report, including styling, charts (Chart.js), data tables, metrics, export functions (PDF, Excel, CSV), scheduling, filtering, sorting, pagination, and drill-down navigation.
      private async generateReportInterface(config: ReportConfig, entityMetadata: any): Promise<string> {
        const reportId = `report_${Date.now()}`;
    
        return `
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>${config.entityType} Report Builder</title>
        <link rel="stylesheet" href="https://sdk.openui5.org/resources/sap/ui/core/themes/sap_horizon/library.css">
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
        <style>
            body {
                font-family: "72", "72full", Arial, Helvetica, sans-serif;
                margin: 0;
                padding: 20px;
                background-color: #f7f7f7;
            }
    
            .report-container {
                max-width: 1400px;
                margin: 0 auto;
                background: white;
                border-radius: 8px;
                box-shadow: 0 2px 8px rgba(0,0,0,0.1);
                overflow: hidden;
            }
    
            .report-header {
                background: linear-gradient(135deg, #0070f3, #0051cc);
                color: white;
                padding: 24px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
    
            .report-title {
                font-size: 24px;
                font-weight: 600;
                margin: 0;
            }
    
            .report-actions {
                display: flex;
                gap: 12px;
            }
    
            .sap-button {
                background: rgba(255,255,255,0.2);
                color: white;
                border: 1px solid rgba(255,255,255,0.3);
                padding: 8px 16px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 14px;
                transition: all 0.2s;
            }
    
            .sap-button:hover {
                background: rgba(255,255,255,0.3);
                transform: translateY(-1px);
            }
    
            .sap-button.primary {
                background: #ff6b35;
                border-color: #ff6b35;
            }
    
            .sap-button.primary:hover {
                background: #e55a2b;
            }
    
            .report-toolbar {
                padding: 16px 24px;
                background: #f9f9f9;
                border-bottom: 1px solid #e6e6e6;
                display: flex;
                flex-wrap: wrap;
                gap: 16px;
                align-items: center;
            }
    
            .filter-group {
                display: flex;
                align-items: center;
                gap: 8px;
            }
    
            .filter-input {
                padding: 6px 12px;
                border: 1px solid #ccc;
                border-radius: 4px;
                font-size: 14px;
            }
    
            .report-content {
                padding: 24px;
            }
    
            .report-tabs {
                display: flex;
                border-bottom: 2px solid #e6e6e6;
                margin-bottom: 24px;
            }
    
            .report-tab {
                padding: 12px 20px;
                cursor: pointer;
                border-bottom: 3px solid transparent;
                font-weight: 500;
                transition: all 0.2s;
            }
    
            .report-tab.active {
                border-bottom-color: #0070f3;
                color: #0070f3;
            }
    
            .report-tab:hover {
                background: #f5f5f5;
            }
    
            .tab-content {
                display: none;
            }
    
            .tab-content.active {
                display: block;
            }
    
            .data-table {
                width: 100%;
                border-collapse: collapse;
                margin-bottom: 20px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }
    
            .data-table th,
            .data-table td {
                padding: 12px;
                text-align: left;
                border-bottom: 1px solid #e6e6e6;
            }
    
            .data-table th {
                background: #f8f9fa;
                font-weight: 600;
                position: sticky;
                top: 0;
                z-index: 10;
                cursor: pointer;
            }
    
            .data-table th:hover {
                background: #e9ecef;
            }
    
            .data-table tr:hover {
                background: #f8f9fa;
            }
    
            .drill-down-link {
                color: #0070f3;
                cursor: pointer;
                text-decoration: underline;
            }
    
            .drill-down-link:hover {
                color: #0051cc;
            }
    
            .chart-container {
                background: white;
                border-radius: 8px;
                padding: 20px;
                margin-bottom: 20px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }
    
            .chart-title {
                font-size: 18px;
                font-weight: 600;
                margin-bottom: 16px;
                color: #333;
            }
    
            .metrics-grid {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
                gap: 16px;
                margin-bottom: 24px;
            }
    
            .metric-card {
                background: white;
                padding: 20px;
                border-radius: 8px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
                text-align: center;
            }
    
            .metric-value {
                font-size: 32px;
                font-weight: 700;
                color: #0070f3;
                margin-bottom: 8px;
            }
    
            .metric-label {
                font-size: 14px;
                color: #666;
                font-weight: 500;
            }
    
            .breadcrumb {
                background: #f0f0f0;
                padding: 12px 16px;
                border-radius: 4px;
                margin-bottom: 16px;
                font-size: 14px;
            }
    
            .breadcrumb-item {
                color: #0070f3;
                cursor: pointer;
                text-decoration: underline;
            }
    
            .breadcrumb-separator {
                margin: 0 8px;
                color: #999;
            }
    
            .export-modal {
                display: none;
                position: fixed;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                background: rgba(0,0,0,0.5);
                z-index: 1000;
            }
    
            .export-dialog {
                position: absolute;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
                background: white;
                padding: 24px;
                border-radius: 8px;
                min-width: 400px;
            }
    
            .export-options {
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
                gap: 12px;
                margin: 16px 0;
            }
    
            .export-option {
                padding: 12px;
                border: 2px solid #e6e6e6;
                border-radius: 4px;
                text-align: center;
                cursor: pointer;
                transition: all 0.2s;
            }
    
            .export-option:hover {
                border-color: #0070f3;
                background: #f0f8ff;
            }
    
            .export-option.selected {
                border-color: #0070f3;
                background: #0070f3;
                color: white;
            }
    
            .loading {
                display: inline-block;
                width: 20px;
                height: 20px;
                border: 3px solid #f3f3f3;
                border-top: 3px solid #0070f3;
                border-radius: 50%;
                animation: spin 1s linear infinite;
            }
    
            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }
    
            .pagination {
                display: flex;
                justify-content: center;
                align-items: center;
                gap: 8px;
                margin-top: 20px;
            }
    
            .pagination button {
                padding: 8px 12px;
                border: 1px solid #ccc;
                background: white;
                cursor: pointer;
                border-radius: 4px;
            }
    
            .pagination button:hover {
                background: #f5f5f5;
            }
    
            .pagination button.active {
                background: #0070f3;
                color: white;
                border-color: #0070f3;
            }
    
            .schedule-section {
                background: #f8f9fa;
                padding: 20px;
                border-radius: 8px;
                margin-bottom: 20px;
            }
    
            .form-group {
                margin-bottom: 16px;
            }
    
            .form-label {
                display: block;
                margin-bottom: 4px;
                font-weight: 500;
                color: #333;
            }
    
            .form-control {
                width: 100%;
                padding: 8px 12px;
                border: 1px solid #ccc;
                border-radius: 4px;
                font-size: 14px;
            }
    
            .form-control:focus {
                outline: none;
                border-color: #0070f3;
                box-shadow: 0 0 0 2px rgba(0,112,243,0.2);
            }
    
            @media (max-width: 768px) {
                .report-toolbar {
                    flex-direction: column;
                    align-items: stretch;
                }
    
                .report-actions {
                    flex-direction: column;
                }
    
                .metrics-grid {
                    grid-template-columns: 1fr;
                }
            }
        </style>
    </head>
    <body>
        <div class="report-container">
            <div class="report-header">
                <h1 class="report-title">${config.entityType} ${config.reportType.charAt(0).toUpperCase() + config.reportType.slice(1)} Report</h1>
                <div class="report-actions">
                    <button class="sap-button" onclick="refreshReport()">
                        <span id="refresh-icon">🔄</span> Refresh
                    </button>
                    <button class="sap-button" onclick="showExportModal()">📤 Export</button>
                    <button class="sap-button primary" onclick="showScheduleModal()">⏰ Schedule</button>
                </div>
            </div>
    
            <div class="report-toolbar">
                <div class="filter-group">
                    <label>Date Range:</label>
                    <input type="date" class="filter-input" id="startDate">
                    <span>to</span>
                    <input type="date" class="filter-input" id="endDate">
                </div>
    
                <div class="filter-group">
                    <label>Quick Filter:</label>
                    <input type="text" class="filter-input" id="quickFilter" placeholder="Search...">
                </div>
    
                <div class="filter-group">
                    <label>Group By:</label>
                    <select class="filter-input" id="groupBy">
                        <option value="">None</option>
                        ${config.dimensions.map(dim => `<option value="${dim}">${dim}</option>`).join('')}
                    </select>
                </div>
    
                <button class="sap-button" onclick="applyFilters()">Apply Filters</button>
                <button class="sap-button" onclick="clearFilters()">Clear</button>
            </div>
    
            <div class="report-content">
                <div class="breadcrumb" id="breadcrumb" style="display: none;">
                    <span class="breadcrumb-item" onclick="navigateToLevel(0)">Summary</span>
                </div>
    
                <div class="report-tabs">
                    <div class="report-tab active" onclick="switchTab('summary')">Summary</div>
                    <div class="report-tab" onclick="switchTab('data')">Data</div>
                    <div class="report-tab" onclick="switchTab('charts')">Charts</div>
                    <div class="report-tab" onclick="switchTab('analytics')">Analytics</div>
                </div>
    
                <div id="summary-tab" class="tab-content active">
                    <div class="metrics-grid">
                        ${config.measures.map(measure => `
                            <div class="metric-card">
                                <div class="metric-value" id="metric-${measure}">-</div>
                                <div class="metric-label">${measure}</div>
                            </div>
                        `).join('')}
                    </div>
    
                    <div class="chart-container">
                        <div class="chart-title">Trend Analysis</div>
                        <canvas id="trendChart" width="400" height="200"></canvas>
                    </div>
                </div>
    
                <div id="data-tab" class="tab-content">
                    <div style="overflow-x: auto;">
                        <table class="data-table" id="dataTable">
                            <thead>
                                <tr>
                                    ${[...config.dimensions, ...config.measures].map(field => `
                                        <th onclick="sortTable('${field}')">${field} ↕️</th>
                                    `).join('')}
                                    ${(config.drillDownLevels?.length || 0) > 0 ? '<th>Actions</th>' : ''}
                                </tr>
                            </thead>
                            <tbody id="dataTableBody">
                                <tr><td colspan="${[...config.dimensions, ...config.measures].length + ((config.drillDownLevels?.length || 0) > 0 ? 1 : 0)}">Loading data...</td></tr>
                            </tbody>
                        </table>
                    </div>
    
                    <div class="pagination" id="pagination"></div>
                </div>
    
                <div id="charts-tab" class="tab-content">
                    ${(config.visualizations || []).map((chart, index) => `
                        <div class="chart-container">
                            <div class="chart-title">${chart.title}</div>
                            <canvas id="chart-${index}" width="400" height="300"></canvas>
                        </div>
                    `).join('')}
                </div>
    
                <div id="analytics-tab" class="tab-content">
                    <div class="chart-container">
                        <div class="chart-title">Advanced Analytics</div>
                        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px;">
                            <canvas id="correlationChart" width="300" height="300"></canvas>
                            <canvas id="distributionChart" width="300" height="300"></canvas>
                        </div>
                    </div>
    
                    <div class="schedule-section">
                        <h3>Statistical Summary</h3>
                        <div id="statisticalSummary">Loading statistical analysis...</div>
                    </div>
                </div>
            </div>
        </div>
    
        <!-- Export Modal -->
        <div id="exportModal" class="export-modal">
            <div class="export-dialog">
                <h3>Export Report</h3>
                <div class="export-options">
                    ${(config.exportFormats || []).map(format => `
                        <div class="export-option" onclick="selectExportFormat('${format}')">
                            ${format.toUpperCase()}
                        </div>
                    `).join('')}
                </div>
                <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
                    <button class="sap-button" onclick="hideExportModal()">Cancel</button>
                    <button class="sap-button primary" onclick="exportReport()">Export</button>
                </div>
            </div>
        </div>
    
        <!-- Schedule Modal -->
        <div id="scheduleModal" class="export-modal">
            <div class="export-dialog">
                <h3>Schedule Report</h3>
                <div class="form-group">
                    <label class="form-label">Frequency:</label>
                    <select class="form-control" id="scheduleFrequency">
                        <option value="daily">Daily</option>
                        <option value="weekly">Weekly</option>
                        <option value="monthly">Monthly</option>
                        <option value="quarterly">Quarterly</option>
                    </select>
                </div>
                <div class="form-group">
                    <label class="form-label">Recipients (comma-separated emails):</label>
                    <input type="text" class="form-control" id="scheduleRecipients" placeholder="user1@company.com, user2@company.com">
                </div>
                <div style="display: flex; gap: 12px; justify-content: flex-end; margin-top: 20px;">
                    <button class="sap-button" onclick="hideScheduleModal()">Cancel</button>
                    <button class="sap-button primary" onclick="scheduleReport()">Schedule</button>
                </div>
            </div>
        </div>
    
        <script>
            // Report state management
            let currentData = [];
            let filteredData = [];
            let currentPage = 1;
            let pageSize = 50;
            let drillDownStack = [];
            let currentLevel = 0;
            let sortColumn = '';
            let sortDirection = 'asc';
            let selectedExportFormat = 'pdf';
    
            // Configuration
            const reportConfig = ${JSON.stringify(config)};
    
            // Initialize report
            document.addEventListener('DOMContentLoaded', function() {
                loadInitialData();
                initializeCharts();
                setDefaultDateRange();
            });
    
            function setDefaultDateRange() {
                const endDate = new Date();
                const startDate = new Date();
                startDate.setMonth(startDate.getMonth() - 1);
    
                document.getElementById('startDate').value = startDate.toISOString().split('T')[0];
                document.getElementById('endDate').value = endDate.toISOString().split('T')[0];
            }
    
            async function loadInitialData() {
                try {
                    showLoading('dataTableBody');
    
                    // Simulate data loading
                    const mockData = generateMockData(100);
                    currentData = mockData;
                    filteredData = [...currentData];
    
                    updateSummaryMetrics();
                    updateDataTable();
                    updateCharts();
                    updateAnalytics();
    
                } catch (error) {
                    this.logger.error('Error loading data:', { error: error });
                    showError('Failed to load report data');
                }
            }
    
            function generateMockData(count) {
                const data = [];
                const statuses = ['Active', 'Inactive', 'Pending', 'Completed'];
                const categories = ['A', 'B', 'C', 'D'];
    
                for (let i = 0; i < count; i++) {
                    const record = {};
    
                    reportConfig.dimensions.forEach(dim => {
                        switch (dim.toLowerCase()) {
                            case 'status':
                                record[dim] = statuses[Math.floor(Math.random() * statuses.length)];
                                break;
                            case 'category':
                                record[dim] = categories[Math.floor(Math.random() * categories.length)];
                                break;
                            case 'date':
                                const date = new Date();
                                date.setDate(date.getDate() - Math.floor(Math.random() * 365));
                                record[dim] = date.toISOString().split('T')[0];
                                break;
                            default:
                                record[dim] = \`\${dim}_\${i + 1}\`;
                        }
                    });
    
                    reportConfig.measures.forEach(measure => {
                        record[measure] = Math.floor(Math.random() * 10000) + 1000;
                    });
    
                    record.id = i + 1;
                    data.push(record);
                }
    
                return data;
            }
    
            function updateSummaryMetrics() {
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const sum = values.reduce((a, b) => a + b, 0);
                    const avg = sum / values.length;
                    const max = Math.max(...values);
    
                    const element = document.getElementById(\`metric-\${measure}\`);
                    if (element) {
                        element.textContent = formatNumber(sum);
                        element.title = \`Average: \${formatNumber(avg)}, Max: \${formatNumber(max)}\`;
                    }
                });
            }
    
            function updateDataTable() {
                const tbody = document.getElementById('dataTableBody');
                const startIndex = (currentPage - 1) * pageSize;
                const endIndex = startIndex + pageSize;
                const pageData = filteredData.slice(startIndex, endIndex);
    
                tbody.innerHTML = pageData.map(row => \`
                    <tr>
                        \${[...reportConfig.dimensions, ...reportConfig.measures].map(field => \`
                            <td>\${formatCellValue(row[field], field)}</td>
                        \`).join('')}
                        \${reportConfig.drillDownLevels.length > 0 ? \`
                            <td>
                                <span class="drill-down-link" onclick="drillDown('\${row.id}', '\${row[reportConfig.dimensions[0]]}')">
                                    Drill Down
                                </span>
                            </td>
                        \` : ''}
                    </tr>
                \`).join('');
    
                updatePagination();
            }
    
            function formatCellValue(value, field) {
                if (value === null || value === undefined) return '-';
    
                if (reportConfig.measures.includes(field)) {
                    return formatNumber(value);
                }
    
                if (field.toLowerCase().includes('date')) {
                    return new Date(value).toLocaleDateString();
                }
    
                return value;
            }
    
            function formatNumber(num) {
                if (num >= 1000000) {
                    return (num / 1000000).toFixed(1) + 'M';
                } else if (num >= 1000) {
                    return (num / 1000).toFixed(1) + 'K';
                }
                return num.toLocaleString();
            }
    
            function updatePagination() {
                const totalPages = Math.ceil(filteredData.length / pageSize);
                const pagination = document.getElementById('pagination');
    
                let paginationHTML = '';
    
                // Previous button
                if (currentPage > 1) {
                    paginationHTML += \`<button onclick="changePage(\${currentPage - 1})">← Previous</button>\`;
                }
    
                // Page numbers
                const startPage = Math.max(1, currentPage - 2);
                const endPage = Math.min(totalPages, currentPage + 2);
    
                for (let i = startPage; i <= endPage; i++) {
                    const activeClass = i === currentPage ? 'active' : '';
                    paginationHTML += \`<button class="\${activeClass}" onclick="changePage(\${i})">\${i}</button>\`;
                }
    
                // Next button
                if (currentPage < totalPages) {
                    paginationHTML += \`<button onclick="changePage(\${currentPage + 1})">Next →</button>\`;
                }
    
                pagination.innerHTML = paginationHTML;
            }
    
            function changePage(page) {
                currentPage = page;
                updateDataTable();
            }
    
            function initializeCharts() {
                // Trend chart
                const trendCtx = document.getElementById('trendChart').getContext('2d');
                new Chart(trendCtx, {
                    type: 'line',
                    data: {
                        labels: getLast12Months(),
                        datasets: reportConfig.measures.map((measure, index) => ({
                            label: measure,
                            data: generateTrendData(),
                            borderColor: getChartColor(index),
                            backgroundColor: getChartColor(index, 0.1),
                            tension: 0.4
                        }))
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Monthly Trend'
                            }
                        }
                    }
                });
    
                // Initialize visualization charts
                reportConfig.visualizations.forEach((chart, index) => {
                    initializeVisualizationChart(chart, index);
                });
    
                // Analytics charts
                initializeAnalyticsCharts();
            }
    
            function initializeVisualizationChart(chartConfig, index) {
                const ctx = document.getElementById(\`chart-\${index}\`).getContext('2d');
    
                new Chart(ctx, {
                    type: chartConfig.type,
                    data: generateChartData(chartConfig),
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            title: {
                                display: true,
                                text: chartConfig.title
                            }
                        }
                    }
                });
            }
    
            function initializeAnalyticsCharts() {
                // Correlation chart
                const correlationCtx = document.getElementById('correlationChart').getContext('2d');
                new Chart(correlationCtx, {
                    type: 'scatter',
                    data: {
                        datasets: [{
                            label: 'Correlation Analysis',
                            data: generateCorrelationData(),
                            backgroundColor: 'rgba(0, 112, 243, 0.6)'
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Measure Correlation'
                            }
                        }
                    }
                });
    
                // Distribution chart
                const distributionCtx = document.getElementById('distributionChart').getContext('2d');
                new Chart(distributionCtx, {
                    type: 'bar',
                    data: {
                        labels: ['Q1', 'Q2', 'Q3', 'Q4'],
                        datasets: [{
                            label: 'Distribution',
                            data: [25, 35, 28, 42],
                            backgroundColor: [
                                'rgba(255, 107, 53, 0.8)',
                                'rgba(0, 112, 243, 0.8)',
                                'rgba(40, 167, 69, 0.8)',
                                'rgba(255, 193, 7, 0.8)'
                            ]
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            title: {
                                display: true,
                                text: 'Quartile Distribution'
                            }
                        }
                    }
                });
            }
    
            function generateChartData(chartConfig) {
                const labels = getUniqueValues(filteredData, chartConfig.xAxis);
                const data = labels.map(label => {
                    const filtered = filteredData.filter(row => row[chartConfig.xAxis] === label);
                    return filtered.reduce((sum, row) => sum + (row[chartConfig.yAxis] || 0), 0);
                });
    
                return {
                    labels: labels,
                    datasets: [{
                        label: chartConfig.yAxis,
                        data: data,
                        backgroundColor: chartConfig.type === 'pie' ?
                            labels.map((_, i) => getChartColor(i, 0.8)) :
                            getChartColor(0, 0.8),
                        borderColor: getChartColor(0),
                        borderWidth: 1
                    }]
                };
            }
    
            function getUniqueValues(data, field) {
                return [...new Set(data.map(row => row[field]))].slice(0, 10);
            }
    
            function generateTrendData() {
                return Array.from({length: 12}, () => Math.floor(Math.random() * 1000) + 500);
            }
    
            function generateCorrelationData() {
                return Array.from({length: 50}, () => ({
                    x: Math.random() * 100,
                    y: Math.random() * 100
                }));
            }
    
            function getLast12Months() {
                const months = [];
                const now = new Date();
                for (let i = 11; i >= 0; i--) {
                    const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
                    months.push(date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }));
                }
                return months;
            }
    
            function getChartColor(index, alpha = 1) {
                const colors = [
                    \`rgba(0, 112, 243, \${alpha})\`,
                    \`rgba(255, 107, 53, \${alpha})\`,
                    \`rgba(40, 167, 69, \${alpha})\`,
                    \`rgba(255, 193, 7, \${alpha})\`,
                    \`rgba(108, 117, 125, \${alpha})\`
                ];
                return colors[index % colors.length];
            }
    
            function updateCharts() {
                // Update existing charts with filtered data
                updateSummaryMetrics();
            }
    
            function updateAnalytics() {
                const summary = document.getElementById('statisticalSummary');
    
                let analyticsHTML = '<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px;">';
    
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const mean = values.reduce((a, b) => a + b, 0) / values.length;
                    const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
                    const stdDev = Math.sqrt(variance);
    
                    analyticsHTML += \`
                        <div class="metric-card">
                            <h4>\${measure}</h4>
                            <p><strong>Mean:</strong> \${formatNumber(mean)}</p>
                            <p><strong>Std Dev:</strong> \${formatNumber(stdDev)}</p>
                            <p><strong>Min:</strong> \${formatNumber(Math.min(...values))}</p>
                            <p><strong>Max:</strong> \${formatNumber(Math.max(...values))}</p>
                        </div>
                    \`;
                });
    
                analyticsHTML += '</div>';
                summary.innerHTML = analyticsHTML;
            }
    
            // Event handlers
            function switchTab(tabName) {
                // Hide all tabs
                document.querySelectorAll('.tab-content').forEach(tab => {
                    tab.classList.remove('active');
                });
                document.querySelectorAll('.report-tab').forEach(tab => {
                    tab.classList.remove('active');
                });
    
                // Show selected tab
                document.getElementById(\`\${tabName}-tab\`).classList.add('active');
                event.target.classList.add('active');
            }
    
            function applyFilters() {
                const startDate = document.getElementById('startDate').value;
                const endDate = document.getElementById('endDate').value;
                const quickFilter = document.getElementById('quickFilter').value.toLowerCase();
                const groupBy = document.getElementById('groupBy').value;
    
                filteredData = currentData.filter(row => {
                    let matchesFilter = true;
    
                    // Date filter
                    if (startDate && endDate && row.date) {
                        const rowDate = new Date(row.date);
                        matchesFilter = matchesFilter && rowDate >= new Date(startDate) && rowDate <= new Date(endDate);
                    }
    
                    // Quick filter
                    if (quickFilter) {
                        const rowText = Object.values(row).join(' ').toLowerCase();
                        matchesFilter = matchesFilter && rowText.includes(quickFilter);
                    }
    
                    return matchesFilter;
                });
    
                // Group by functionality
                if (groupBy) {
                    // Implement grouping logic here
                    this.logger.debug('Grouping by:', groupBy);
                }
    
                currentPage = 1;
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
                updateAnalytics();
            }
    
            function clearFilters() {
                document.getElementById('startDate').value = '';
                document.getElementById('endDate').value = '';
                document.getElementById('quickFilter').value = '';
                document.getElementById('groupBy').value = '';
    
                filteredData = [...currentData];
                currentPage = 1;
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
                updateAnalytics();
            }
    
            function sortTable(column) {
                if (sortColumn === column) {
                    sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
                } else {
                    sortColumn = column;
                    sortDirection = 'asc';
                }
    
                filteredData.sort((a, b) => {
                    let aVal = a[column];
                    let bVal = b[column];
    
                    if (typeof aVal === 'string') {
                        aVal = aVal.toLowerCase();
                        bVal = bVal.toLowerCase();
                    }
    
                    if (sortDirection === 'asc') {
                        return aVal > bVal ? 1 : -1;
                    } else {
                        return aVal < bVal ? 1 : -1;
                    }
                });
    
                updateDataTable();
            }
    
            function drillDown(id, value) {
                drillDownStack.push({
                    level: currentLevel,
                    data: [...filteredData],
                    title: \`\${reportConfig.entityType} Report\`
                });
    
                currentLevel++;
    
                // Filter data for drill-down
                filteredData = currentData.filter(row => row[reportConfig.dimensions[0]] === value);
    
                // Update breadcrumb
                updateBreadcrumb(value);
    
                // Update displays
                updateDataTable();
                updateSummaryMetrics();
                updateCharts();
            }
    
            function navigateToLevel(level) {
                if (level < currentLevel && level >= 0) {
                    const targetState = drillDownStack[level];
                    if (targetState) {
                        filteredData = targetState.data;
                        currentLevel = level;
                        drillDownStack = drillDownStack.slice(0, level);
    
                        updateBreadcrumb();
                        updateDataTable();
                        updateSummaryMetrics();
                        updateCharts();
                    }
                }
            }
    
            function updateBreadcrumb(currentItem) {
                const breadcrumb = document.getElementById('breadcrumb');
    
                if (currentLevel === 0) {
                    breadcrumb.style.display = 'none';
                    return;
                }
    
                breadcrumb.style.display = 'block';
                let breadcrumbHTML = '<span class="breadcrumb-item" onclick="navigateToLevel(0)">Summary</span>';
    
                drillDownStack.forEach((item, index) => {
                    breadcrumbHTML += \`<span class="breadcrumb-separator">›</span><span class="breadcrumb-item" onclick="navigateToLevel(\${index + 1})">\${item.title}</span>\`;
                });
    
                if (currentItem) {
                    breadcrumbHTML += \`<span class="breadcrumb-separator">›</span><span>\${currentItem}</span>\`;
                }
    
                breadcrumb.innerHTML = breadcrumbHTML;
            }
    
            async function refreshReport() {
                const icon = document.getElementById('refresh-icon');
                icon.innerHTML = '<div class="loading"></div>';
    
                try {
                    await loadInitialData();
                    showMessage('Report refreshed successfully', 'success');
                } catch (error) {
                    showMessage('Failed to refresh report', 'error');
                } finally {
                    icon.innerHTML = '🔄';
                }
            }
    
            function showExportModal() {
                document.getElementById('exportModal').style.display = 'block';
            }
    
            function hideExportModal() {
                document.getElementById('exportModal').style.display = 'none';
            }
    
            function selectExportFormat(format) {
                document.querySelectorAll('.export-option').forEach(option => {
                    option.classList.remove('selected');
                });
                event.target.classList.add('selected');
                selectedExportFormat = format;
            }
    
            async function exportReport() {
                try {
                    switch (selectedExportFormat) {
                        case 'pdf':
                            await exportToPDF();
                            break;
                        case 'excel':
                            await exportToExcel();
                            break;
                        case 'csv':
                            await exportToCSV();
                            break;
                        case 'json':
                            await exportToJSON();
                            break;
                    }
    
                    showMessage(\`Report exported as \${selectedExportFormat.toUpperCase()}\`, 'success');
                    hideExportModal();
                } catch (error) {
                    showMessage('Export failed', 'error');
                }
            }
    
            async function exportToPDF() {
                const { jsPDF } = window.jspdf;
                const doc = new jsPDF();
    
                doc.setFontSize(20);
                doc.text(\`\${reportConfig.entityType} Report\`, 20, 30);
    
                doc.setFontSize(12);
                doc.text(\`Generated: \${new Date().toLocaleDateString()}\`, 20, 45);
                doc.text(\`Records: \${filteredData.length}\`, 20, 55);
    
                // Add summary metrics
                let yPos = 75;
                doc.setFontSize(16);
                doc.text('Summary Metrics', 20, yPos);
                yPos += 15;
    
                reportConfig.measures.forEach(measure => {
                    const values = filteredData.map(row => row[measure] || 0);
                    const sum = values.reduce((a, b) => a + b, 0);
                    doc.setFontSize(12);
                    doc.text(\`\${measure}: \${formatNumber(sum)}\`, 20, yPos);
                    yPos += 10;
                });
    
                doc.save(\`\${reportConfig.entityType}_report.pdf\`);
            }
    
            async function exportToExcel() {
                const ws = XLSX.utils.json_to_sheet(filteredData);
                const wb = XLSX.utils.book_new();
                XLSX.utils.book_append_sheet(wb, ws, 'Report Data');
    
                XLSX.writeFile(wb, \`\${reportConfig.entityType}_report.xlsx\`);
            }
    
            async function exportToCSV() {
                const headers = [...reportConfig.dimensions, ...reportConfig.measures];
                const csvContent = [
                    headers.join(','),
                    ...filteredData.map(row => headers.map(header => row[header] || '').join(','))
                ].join('\\n');
    
                const blob = new Blob([csvContent], { type: 'text/csv' });
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = \`\${reportConfig.entityType}_report.csv\`;
                a.click();
                window.URL.revokeObjectURL(url);
            }
    
            async function exportToJSON() {
                const jsonContent = JSON.stringify(filteredData, null, 2);
                const blob = new Blob([jsonContent], { type: 'application/json' });
                const url = window.URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = \`\${reportConfig.entityType}_report.json\`;
                a.click();
                window.URL.revokeObjectURL(url);
            }
    
            function showScheduleModal() {
                document.getElementById('scheduleModal').style.display = 'block';
            }
    
            function hideScheduleModal() {
                document.getElementById('scheduleModal').style.display = 'none';
            }
    
            async function scheduleReport() {
                const frequency = document.getElementById('scheduleFrequency').value;
                const recipients = document.getElementById('scheduleRecipients').value;
    
                if (!recipients.trim()) {
                    showMessage('Please enter at least one recipient email', 'error');
                    return;
                }
    
                try {
                    // Here you would call your scheduling API
                    this.logger.debug('Scheduling report:', { frequency, recipients });
                    showMessage(\`Report scheduled \${frequency} for \${recipients}\`, 'success');
                    hideScheduleModal();
                } catch (error) {
                    showMessage('Failed to schedule report', 'error');
                }
            }
    
            function showMessage(message, type) {
                // Create toast notification
                const toast = document.createElement('div');
                toast.style.cssText = \`
                    position: fixed;
                    top: 20px;
                    right: 20px;
                    padding: 12px 20px;
                    border-radius: 4px;
                    color: white;
                    font-weight: 500;
                    z-index: 2000;
                    background: \${type === 'success' ? '#28a745' : '#dc3545'};
                    animation: slideIn 0.3s ease;
                \`;
                toast.textContent = message;
    
                document.body.appendChild(toast);
    
                setTimeout(() => {
                    toast.remove();
                }, 3000);
            }
    
            function showLoading(elementId) {
                const element = document.getElementById(elementId);
                if (element) {
                    element.innerHTML = '<div style="text-align: center; padding: 20px;"><div class="loading"></div></div>';
                }
            }
    
            function showError(message) {
                const element = document.getElementById('dataTableBody');
                if (element) {
                    element.innerHTML = \`<tr><td colspan="10" style="text-align: center; color: red; padding: 20px;">\${message}</td></tr>\`;
                }
            }
    
            // Add CSS animation
            const style = document.createElement('style');
            style.textContent = \`
                @keyframes slideIn {
                    from {
                        transform: translateX(100%);
                        opacity: 0;
                    }
                    to {
                        transform: translateX(0);
                        opacity: 1;
                    }
                }
            \`;
            document.head.appendChild(style);
        </script>
    </body>
    </html>`;
      }
  • Tool registration in authentication scope mapping, associating 'ui-report-builder' with required scope 'ui.reports'.
    const scopeMapping: Record<string, string> = {
      'ui-form-generator': 'ui.forms',
      'ui-data-grid': 'ui.grids',
      'ui-dashboard-composer': 'ui.dashboards',
      'ui-workflow-builder': 'ui.workflows',
      'ui-report-builder': 'ui.reports',
    };
  • Tool registration in intelligent router's UI sequence mapping, linking 'ui-report-builder' to 'uiReport' workflow sequence.
      'ui-report-builder': 'uiReport',
    };
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'creates' reports, implying a write operation, but does not specify permissions required, whether reports are saved or transient, any rate limits, or error handling. The phrase 'drill-down reports with analytical capabilities' hints at interactive features but lacks concrete behavioral details. This is inadequate for a tool with mutation implications and no annotation support.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence: 'Creates comprehensive drill-down reports with analytical capabilities.' It is front-loaded with the core action and resource, with no wasted words or redundant information. Every part of the sentence contributes to understanding the tool's purpose, making it highly concise and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of a report creation tool with no annotations and no output schema, the description is insufficient. It lacks details on behavioral aspects (e.g., what 'creates' entails, permissions, or output format), does not differentiate from siblings, and provides no usage guidelines. While the input schema is well-documented, the overall context for safe and effective tool invocation is incomplete, especially for a tool that likely involves data manipulation and UI integration.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, with clear documentation for all four parameters (entityType, reportType, dimensions, measures). The description does not add any parameter-specific information beyond what the schema provides, such as examples or usage tips. According to the rules, with high schema coverage (>80%), the baseline score is 3, as the schema adequately handles parameter semantics without additional description input.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Creates comprehensive drill-down reports with analytical capabilities.' It specifies the verb ('creates') and resource ('reports') with additional context about their nature ('drill-down' and 'analytical capabilities'). However, it does not explicitly differentiate from sibling tools like 'kpi-dashboard-builder' or 'ui-dashboard-composer,' which might also involve report or dashboard creation, leaving some ambiguity in distinguishing its unique role.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. It does not mention any specific contexts, prerequisites, or exclusions, nor does it refer to sibling tools. For example, it lacks clarity on whether this is for UI-specific reports compared to other analytical tools like 'business-intelligence-insights' or 'smart-data-analysis.' This absence of usage guidelines leaves the agent without direction on tool selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Raistlin82/btp-sap-odata-to-mcp-server-optimized'

If you have feedback or need assistance with the MCP directory API, please join our Discord server