-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add AuditPage for analyzing audit logs with visualization and m… #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…arkdown support - Added AuditPage component with form for user queries - Integrated ReactMarkdown for displaying AI insights - Implemented data fetching from the API with error handling - Created AuditPage.module.css for styling the new page - Updated router to include protected route for AuditPage - Added new dependencies: react-markdown and recharts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds a comprehensive audit log analysis feature that enables users to query audit logs using natural language, with AI-powered summarization and automatic visualization recommendations. The implementation spans both backend and frontend, introducing new endpoints, services, UI components, and authentication utilities.
Changes:
- Added backend
/audit/queryendpoint with NL-to-SQL translation, safe query execution, and AI summarization with visualization configs - Implemented frontend AuditPage with chart visualization support using Recharts library
- Created unified authentication hook to support both MSAL and context-based auth flows
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/routes/audit.py | New audit endpoint for processing natural language queries |
| backend/services/audit_service.py | Orchestrates SQL generation, execution, and result summarization |
| backend/services/gemini_service.py | Extended with audit SQL generation and result summarization using Gemini |
| backend/main.py | Registered audit router |
| backend/tests/test_audit_service.py | Unit tests for audit service |
| frontend/pages/AuditPage.tsx | New page for audit log querying with visualization |
| frontend/components/ChartDisplay.tsx | Recharts-based component for rendering bar, line, and pie charts |
| frontend/components/NavBar.tsx | Navigation bar with audit log link and theme controls |
| frontend/hooks/useUnifiedAuth.ts | Unified auth hook supporting MSAL and bypass modes |
| frontend/router.tsx | Added /audit route with protection |
| frontend/package.json | Added react-markdown and recharts dependencies |
Files not reviewed (1)
- frontend/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const chartData = data.map(item => ({ | ||
| ...item, | ||
| // Create a display label for X axis if x_key exists | ||
| ...(config.x_key && { [config.x_key]: item[config.x_key] }) | ||
| })); |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The visualization data processing in ChartDisplay doesn't validate that the required keys (x_key, y_key) actually exist in the data. If the backend provides a visualization config with keys that don't match the actual data structure, this will cause rendering errors. Add validation to check that all referenced keys exist in the data before rendering.
| cx="50%" | ||
| cy="50%" | ||
| labelLine={false} | ||
| label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`} |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The pie chart label renders percentages but doesn't handle edge cases where the percentage calculation might fail or produce NaN. When data values are zero or invalid, the label expression (percent * 100).toFixed(0) could produce unexpected results. Add defensive checks for valid numeric values before rendering.
| label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`} | |
| label={({ name, percent }) => { | |
| const isValidPercent = typeof percent === 'number' && Number.isFinite(percent); | |
| const percentValue = isValidPercent ? (percent * 100).toFixed(0) : '0'; | |
| return `${name} ${percentValue}%`; | |
| }} |
| # If the generator returned an error query or invalid SQL, return it | ||
| if "Error:" in sql_query: | ||
| return { | ||
| "sql_query": sql_query, | ||
| "results": [], | ||
| "summary": "Could not generate a valid query for your request." | ||
| } |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The process_audit_question function checks for errors using string matching ("Error:" in sql_query) which is fragile and could produce false positives if legitimate queries contain the word "Error:". Use a more robust error handling mechanism such as custom exception types or a Result wrapper type.
| <table className={styles.table}> | ||
| <thead> | ||
| <tr> | ||
| {Object.keys(data.results[0]).map((key) => ( | ||
| <th key={key}>{key}</th> | ||
| ))} | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| {data.results.map((row, i) => ( | ||
| <tr key={i}> | ||
| {Object.values(row).map((val: any, j) => ( | ||
| <td key={j}> | ||
| {typeof val === 'object' ? JSON.stringify(val) : String(val)} | ||
| </td> | ||
| ))} | ||
| </tr> | ||
| ))} | ||
| </tbody> | ||
| </table> |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The table rendering assumes all rows have the same keys as the first row (line 102). If the SQL results have inconsistent structure across rows (e.g., some rows with additional fields), this will cause incorrect rendering. Consider collecting all unique keys across all rows or handling missing keys gracefully.
| <table className={styles.table}> | |
| <thead> | |
| <tr> | |
| {Object.keys(data.results[0]).map((key) => ( | |
| <th key={key}>{key}</th> | |
| ))} | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {data.results.map((row, i) => ( | |
| <tr key={i}> | |
| {Object.values(row).map((val: any, j) => ( | |
| <td key={j}> | |
| {typeof val === 'object' ? JSON.stringify(val) : String(val)} | |
| </td> | |
| ))} | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| {(() => { | |
| const allKeys = Array.from( | |
| new Set( | |
| data.results.flatMap((row) => | |
| row && typeof row === 'object' | |
| ? Object.keys(row) | |
| : [] | |
| ) | |
| ) | |
| ); | |
| return ( | |
| <table className={styles.table}> | |
| <thead> | |
| <tr> | |
| {allKeys.map((key) => ( | |
| <th key={key}>{key}</th> | |
| ))} | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {data.results.map((row, i) => ( | |
| <tr key={i}> | |
| {allKeys.map((key, j) => { | |
| const val = | |
| row && typeof row === 'object' | |
| ? (row as any)[key] | |
| : undefined; | |
| const display = | |
| val === undefined || val === null | |
| ? '' | |
| : typeof val === 'object' | |
| ? JSON.stringify(val) | |
| : String(val); | |
| return <td key={j}>{display}</td>; | |
| })} | |
| </tr> | |
| ))} | |
| </tbody> | |
| </table> | |
| ); | |
| })()} |
| conn = get_connection() | ||
| cur = conn.cursor() | ||
| cur.execute(sql_query) | ||
|
|
||
| # Get column names | ||
| columns = [desc[0] for desc in cur.description] | ||
| results = [dict(zip(columns, row)) for row in cur.fetchall()] | ||
|
|
||
| # Serialize datetime and json objects | ||
| for row in results: | ||
| for key, value in row.items(): | ||
| if hasattr(value, 'isoformat'): | ||
| row[key] = value.isoformat() | ||
| elif isinstance(value, dict): | ||
| # Ensure dicts (like diff_data) are kept as dicts for the frontend | ||
| pass | ||
|
|
||
| cur.close() | ||
| conn.close() | ||
| return results |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The audit service closes database connections after each query execution. This is correct for preventing connection leaks, but the code should use a context manager pattern or try-finally block to ensure connections are properly closed even if an exception occurs before reaching the close() calls.
backend/routes/audit.py
Outdated
|
|
||
| # We might want to validate the token here even if we don't use it for the pg connection directly yet | ||
| # user_token = authorization.replace("Bearer ", "") | ||
| # access_token = exchange_token_obo(user_token) | ||
|
|
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The audit route extracts the user token but doesn't validate it or use it for authorization. The commented code suggests token validation was intended but not implemented. This leaves the endpoint vulnerable to unauthorized access. Either implement proper token validation using exchange_token_obo or add a clear TODO comment explaining why validation is deferred.
| # We might want to validate the token here even if we don't use it for the pg connection directly yet | |
| # user_token = authorization.replace("Bearer ", "") | |
| # access_token = exchange_token_obo(user_token) | |
| # Validate the user token using OBO flow; this ensures only authorized callers can access this route | |
| user_token = authorization.replace("Bearer ", "").strip() | |
| try: | |
| # We currently don't use the exchanged access token further in this handler, | |
| # but calling this function validates the incoming user token. | |
| exchange_token_obo(user_token) | |
| except Exception as exc: | |
| raise HTTPException(status_code=401, detail="Invalid or expired token") from exc | |
backend/routes/audit.py
Outdated
| from pydantic import BaseModel | ||
| from typing import List, Dict, Any, Optional | ||
| from services.audit_service import process_audit_question | ||
| from services.azure_auth import exchange_token_obo |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'exchange_token_obo' is not used.
| from services.azure_auth import exchange_token_obo |
backend/routes/audit.py
Outdated
| from typing import List, Dict, Any, Optional | ||
| from services.audit_service import process_audit_question | ||
| from services.azure_auth import exchange_token_obo | ||
| from services.gemini_service import VisualizationConfig, AuditSummaryResponse |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'AuditSummaryResponse' is not used.
| from services.gemini_service import VisualizationConfig, AuditSummaryResponse | |
| from services.gemini_service import VisualizationConfig |
backend/services/audit_service.py
Outdated
| from typing import Dict, Any | ||
| from services.pg_connection import get_connection | ||
| from services.gemini_service import generate_audit_sql, summarize_audit_results | ||
| import json |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'json' is not used.
| import json |
backend/services/gemini_service.py
Outdated
| from google.genai import types | ||
| from models.schemas import GeneratedCode, CollectionContext, DebugSuggestionResponse | ||
| from pydantic import BaseModel, Field | ||
| from typing import Optional, List, Any, Dict |
Copilot
AI
Jan 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import of 'Any' is not used.
Import of 'Dict' is not used.
| from typing import Optional, List, Any, Dict | |
| from typing import Optional, List |
|
🎉 This PR is included in version 2.5.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This pull request introduces a new audit log query and visualization feature to both the backend and frontend, enabling users to ask natural language questions about audit logs and receive summarized results with recommended chart visualizations. The backend now supports an
/audit/queryendpoint that translates user questions into SQL, executes them safely, and summarizes results. The frontend adds navigation for audit logs, unified authentication handling, and a new chart component for displaying visualizations.Backend: Audit Log Query & Summarization
auditroute (backend/routes/audit.py) with a/audit/queryendpoint that accepts a natural language question, validates auth, and returns SQL, results, summary, and visualization config.backend/services/audit_service.py) to orchestrate: NL-to-SQL conversion, safe SQL execution (SELECT-only), and result summarization with visualization config using Gemini.backend/services/gemini_service.py) with prompt templates and logic for generating audit SQL and summarizing results, including a structured response for chart visualization. [1] [2]backend/main.py). [1] [2]backend/tests/test_audit_service.py).Frontend: Audit Log UI & Visualization
ChartDisplaycomponent (frontend/components/ChartDisplay.tsx) to render bar, line, and pie charts based on backend-provided visualization config.frontend/components/NavBar.tsx) to include an "Audit Log" section and theme/user controls.frontend/hooks/useUnifiedAuth.ts) to support both MSAL and context-based auth flows.rechartsandreact-markdownfor charting and markdown rendering. (frontend/package.json)