feat(reports): store and browse report artifacts
This commit is contained in:
1 parent
5967a43eb5
commit
6f2e479958
13 files changed
+875
-93
No files matched your search
+83
-1
@@ -3,9 +3,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
|
||||
from flask import Flask, abort, jsonify, render_template, request
|
||||
from flask import Flask, Response, abort, jsonify, render_template, request
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from ..utils.constants import (
|
||||
APP_REPORT_DATA_DIR,
|
||||
@@ -154,6 +158,84 @@ def register_routes(app: Flask,
|
||||
|
||||
# --- reports -----------------------------------------------------------
|
||||
|
||||
@app.get("/api/reports")
|
||||
def list_report_artifacts():
|
||||
source = (request.args.get("source") or "").strip() or None
|
||||
if source is not None and source not in report_sources:
|
||||
return jsonify({"error": "unknown report source"}), 400
|
||||
limit = request.args.get("limit", default=50, type=int)
|
||||
try:
|
||||
from ..db import get_connection
|
||||
from ..db import repositories as repo
|
||||
result = repo.list_report_artifacts(
|
||||
get_connection(), source=source,
|
||||
report_type=(request.args.get("type") or "").strip() or None,
|
||||
since=(request.args.get("since") or "").strip() or None,
|
||||
until=(request.args.get("until") or "").strip() or None,
|
||||
limit=limit,
|
||||
cursor=(request.args.get("cursor") or "").strip() or None,
|
||||
)
|
||||
return jsonify(result)
|
||||
except ValueError as exc:
|
||||
return jsonify({"error": str(exc)}), 400
|
||||
except Exception as exc:
|
||||
return jsonify({"error": f"report database unavailable: {exc}"}), 503
|
||||
|
||||
@app.get("/api/report-artifacts/<int:artifact_id>")
|
||||
def get_report_artifact(artifact_id: int):
|
||||
try:
|
||||
from ..db import get_connection
|
||||
from ..db import repositories as repo
|
||||
artifact = repo.get_report_artifact(get_connection(), artifact_id)
|
||||
except Exception as exc:
|
||||
return jsonify({"error": f"report database unavailable: {exc}"}), 503
|
||||
if artifact is None:
|
||||
abort(404, "report artifact not found")
|
||||
return jsonify(artifact)
|
||||
|
||||
@app.get("/api/report-artifacts/<int:artifact_id>/export")
|
||||
def export_report_artifact(artifact_id: int):
|
||||
try:
|
||||
from ..db import get_connection
|
||||
from ..db import repositories as repo
|
||||
artifact = repo.get_report_artifact(get_connection(), artifact_id)
|
||||
except Exception as exc:
|
||||
return jsonify({"error": f"report database unavailable: {exc}"}), 503
|
||||
if artifact is None:
|
||||
abort(404, "report artifact not found")
|
||||
|
||||
payload = artifact["payload"]
|
||||
export_format = (request.args.get("format") or "json").lower()
|
||||
filename = secure_filename(os.path.basename(
|
||||
artifact["original_path"])) or f"report-{artifact_id}"
|
||||
if export_format == "json":
|
||||
content = json.dumps(payload, ensure_ascii=False, indent=2)
|
||||
mimetype = "application/json"
|
||||
extension = ".json"
|
||||
elif export_format == "txt" and payload.get("format") == "text":
|
||||
content = payload.get("content", "")
|
||||
mimetype = "text/plain; charset=utf-8"
|
||||
extension = ".txt"
|
||||
elif export_format == "csv" and payload.get("format") == "csv":
|
||||
output = io.StringIO(newline="")
|
||||
writer = csv.DictWriter(
|
||||
output, fieldnames=payload.get("columns", []),
|
||||
extrasaction="ignore")
|
||||
writer.writeheader()
|
||||
writer.writerows(payload.get("rows", []))
|
||||
content = output.getvalue()
|
||||
mimetype = "text/csv; charset=utf-8"
|
||||
extension = ".csv"
|
||||
else:
|
||||
return jsonify({"error": "unsupported export format"}), 406
|
||||
|
||||
download_name = (filename if filename.lower().endswith(extension)
|
||||
else f"{filename}{extension}")
|
||||
response = Response(content, mimetype=mimetype)
|
||||
response.headers["Content-Disposition"] = (
|
||||
f'attachment; filename="{download_name}"')
|
||||
return response
|
||||
|
||||
@app.get("/api/reports/<source>")
|
||||
def list_reports(source):
|
||||
base = report_sources.get(source)
|
||||
|
||||
Reference in new issue
Block a user