Extract HTML5 project controller
This commit is contained in:
@@ -0,0 +1,136 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from collections.abc import AsyncIterator, Callable, Iterable
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
from api_server.html5_authoring import render_html5_authoring_changes
|
||||||
|
from api_server.html5_editor import (
|
||||||
|
render_html5_source,
|
||||||
|
render_html5_status,
|
||||||
|
render_html5_symbol_detail,
|
||||||
|
render_html5_symbols,
|
||||||
|
)
|
||||||
|
from api_server.html5_forms import form_value
|
||||||
|
from api_server.html5_inspector import render_html5_flowchart, render_html5_project_report, render_html5_review
|
||||||
|
from api_server.html5_projects import render_html5_index, render_html5_project_rows
|
||||||
|
from api_server.html5_sse import html5_sse_comment, html5_sse_if_changed
|
||||||
|
from sir import SirSnapshot
|
||||||
|
|
||||||
|
|
||||||
|
def html5_index_page(projects: Iterable[object]) -> str:
|
||||||
|
return render_html5_index(projects)
|
||||||
|
|
||||||
|
|
||||||
|
async def html5_create_project_rows(
|
||||||
|
*,
|
||||||
|
form: dict[str, list[str]],
|
||||||
|
create_project: Callable[[object], Any],
|
||||||
|
create_request: Callable[..., object],
|
||||||
|
project_summaries: Callable[[], Iterable[object]],
|
||||||
|
) -> str:
|
||||||
|
project_id = form_value(form, "project_id")
|
||||||
|
if not project_id:
|
||||||
|
raise HTTPException(status_code=400, detail="project_id is required.")
|
||||||
|
await create_project(create_request(project_id=project_id, name=form_value(form, "name")))
|
||||||
|
return render_html5_project_rows(project_summaries())
|
||||||
|
|
||||||
|
|
||||||
|
async def html5_delete_project_rows(
|
||||||
|
*,
|
||||||
|
project_id: str,
|
||||||
|
form: dict[str, list[str]],
|
||||||
|
delete_project: Callable[[str, object], Any],
|
||||||
|
delete_request: Callable[..., object],
|
||||||
|
project_summaries: Callable[[], Iterable[object]],
|
||||||
|
) -> str:
|
||||||
|
await delete_project(project_id, delete_request(confirmation=form_value(form, "confirmation") or ""))
|
||||||
|
return render_html5_project_rows(project_summaries())
|
||||||
|
|
||||||
|
|
||||||
|
async def html5_project_event_stream(
|
||||||
|
*,
|
||||||
|
project_id: str,
|
||||||
|
project_snapshot: Callable[[str], SirSnapshot],
|
||||||
|
project_report: Callable[[str], Any],
|
||||||
|
review: Callable[[str], Any],
|
||||||
|
flowchart: Callable[..., Any],
|
||||||
|
authoring_changes: Callable[[str], Iterable[object]],
|
||||||
|
once: bool = False,
|
||||||
|
) -> AsyncIterator[str]:
|
||||||
|
last_fragments: dict[str, str] = {}
|
||||||
|
while True:
|
||||||
|
yield html5_sse_comment(f"project {project_id} heartbeat")
|
||||||
|
try:
|
||||||
|
snapshot = project_snapshot(project_id)
|
||||||
|
status = render_html5_status(project_id, snapshot)
|
||||||
|
report = await project_report(project_id)
|
||||||
|
findings = await review(project_id)
|
||||||
|
graph = await flowchart(project_id, focus=None, depth=1, limit=80)
|
||||||
|
except HTTPException as error:
|
||||||
|
status = f'<span>project: {project_id}</span><span>error: {error.detail}</span>'
|
||||||
|
report = None
|
||||||
|
findings = None
|
||||||
|
graph = None
|
||||||
|
for event_text in html5_sse_if_changed(last_fragments, "status", status):
|
||||||
|
yield event_text
|
||||||
|
for event_text in html5_sse_if_changed(
|
||||||
|
last_fragments,
|
||||||
|
"authoring-changes",
|
||||||
|
render_html5_authoring_changes(project_id, authoring_changes(project_id)),
|
||||||
|
):
|
||||||
|
yield event_text
|
||||||
|
if report is not None:
|
||||||
|
for event_text in html5_sse_if_changed(last_fragments, "project-report", render_html5_project_report(project_id, report)):
|
||||||
|
yield event_text
|
||||||
|
if findings is not None:
|
||||||
|
for event_text in html5_sse_if_changed(last_fragments, "project-review", render_html5_review(project_id, findings)):
|
||||||
|
yield event_text
|
||||||
|
if graph is not None:
|
||||||
|
for event_text in html5_sse_if_changed(last_fragments, "project-flowchart", render_html5_flowchart(project_id, graph)):
|
||||||
|
yield event_text
|
||||||
|
if once:
|
||||||
|
break
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
def html5_project_symbols(*, snapshot: SirSnapshot, q: str, project_id: str) -> str:
|
||||||
|
return render_html5_symbols(snapshot, q, project_id)
|
||||||
|
|
||||||
|
|
||||||
|
def html5_project_symbol_detail(*, project_id: str, references: object) -> str:
|
||||||
|
return render_html5_symbol_detail(project_id, references)
|
||||||
|
|
||||||
|
|
||||||
|
def html5_project_source_by_path(*, snapshot: SirSnapshot, path: str) -> str:
|
||||||
|
node = next(
|
||||||
|
(
|
||||||
|
item
|
||||||
|
for item in snapshot.nodes
|
||||||
|
if item.source_ref is not None and item.source_ref.source_path == path
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
if node is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Source not found: {path}")
|
||||||
|
return render_html5_source(node)
|
||||||
|
|
||||||
|
|
||||||
|
def html5_project_source_by_lineage(*, node: object | None, lineage_id: str) -> str:
|
||||||
|
if node is None:
|
||||||
|
raise HTTPException(status_code=404, detail=f"Lineage not found: {lineage_id}")
|
||||||
|
return render_html5_source(node)
|
||||||
|
|
||||||
|
|
||||||
|
def html5_project_report_fragment(*, project_id: str, report: object) -> str:
|
||||||
|
return render_html5_project_report(project_id, report)
|
||||||
|
|
||||||
|
|
||||||
|
def html5_project_review_fragment(*, project_id: str, findings: object) -> str:
|
||||||
|
return render_html5_review(project_id, findings)
|
||||||
|
|
||||||
|
|
||||||
|
def html5_project_flowchart_fragment(*, project_id: str, flowchart: object, focus: str | None, depth: int) -> str:
|
||||||
|
return render_html5_flowchart(project_id, flowchart, focus=focus, depth=depth)
|
||||||
@@ -37,7 +37,6 @@ from neo4j import AsyncGraphDatabase
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from api_server.html5_forms import (
|
from api_server.html5_forms import (
|
||||||
form_value as _form_value,
|
|
||||||
html5_form_data as _html5_form_data,
|
html5_form_data as _html5_form_data,
|
||||||
)
|
)
|
||||||
from api_server.html5_editor_controller import (
|
from api_server.html5_editor_controller import (
|
||||||
@@ -45,22 +44,24 @@ from api_server.html5_editor_controller import (
|
|||||||
html5_form_editor_page as _html5_form_editor_page,
|
html5_form_editor_page as _html5_form_editor_page,
|
||||||
html5_object_context_fragment as _html5_object_context_fragment,
|
html5_object_context_fragment as _html5_object_context_fragment,
|
||||||
)
|
)
|
||||||
from api_server.html5_projects import render_html5_index, render_html5_project_rows
|
from api_server.html5_project_controller import (
|
||||||
|
html5_create_project_rows as _html5_create_project_rows,
|
||||||
|
html5_delete_project_rows as _html5_delete_project_rows,
|
||||||
|
html5_index_page as _html5_index_page,
|
||||||
|
html5_project_event_stream as _html5_project_event_stream,
|
||||||
|
html5_project_flowchart_fragment as _html5_project_flowchart_fragment,
|
||||||
|
html5_project_report_fragment as _html5_project_report_fragment,
|
||||||
|
html5_project_review_fragment as _html5_project_review_fragment,
|
||||||
|
html5_project_source_by_lineage as _html5_project_source_by_lineage,
|
||||||
|
html5_project_source_by_path as _html5_project_source_by_path,
|
||||||
|
html5_project_symbol_detail as _html5_project_symbol_detail,
|
||||||
|
html5_project_symbols as _html5_project_symbols,
|
||||||
|
)
|
||||||
from api_server.html5_responses import (
|
from api_server.html5_responses import (
|
||||||
Html5StaticFiles,
|
Html5StaticFiles,
|
||||||
html5_response as _html5_response,
|
html5_response as _html5_response,
|
||||||
html5_sse_response as _html5_sse_response,
|
html5_sse_response as _html5_sse_response,
|
||||||
)
|
)
|
||||||
from api_server.html5_sse import (
|
|
||||||
html5_sse_comment as _html5_sse_comment,
|
|
||||||
html5_sse_if_changed as _html5_sse_if_changed,
|
|
||||||
)
|
|
||||||
from api_server.html5_inspector import (
|
|
||||||
render_html5_flowchart,
|
|
||||||
render_html5_project_report,
|
|
||||||
render_html5_review,
|
|
||||||
)
|
|
||||||
from api_server.html5_authoring import render_html5_authoring_changes
|
|
||||||
from api_server.html5_authoring_controller import (
|
from api_server.html5_authoring_controller import (
|
||||||
html5_authoring_apply_change_set as _html5_authoring_apply_change_set,
|
html5_authoring_apply_change_set as _html5_authoring_apply_change_set,
|
||||||
html5_authoring_apply_metadata_object as _html5_authoring_apply_metadata_object,
|
html5_authoring_apply_metadata_object as _html5_authoring_apply_metadata_object,
|
||||||
@@ -71,12 +72,6 @@ from api_server.html5_authoring_controller import (
|
|||||||
html5_authoring_metadata_object_preview as _html5_authoring_metadata_object_preview,
|
html5_authoring_metadata_object_preview as _html5_authoring_metadata_object_preview,
|
||||||
html5_authoring_semantic_diff_preview as _html5_authoring_semantic_diff_preview,
|
html5_authoring_semantic_diff_preview as _html5_authoring_semantic_diff_preview,
|
||||||
)
|
)
|
||||||
from api_server.html5_editor import (
|
|
||||||
render_html5_source,
|
|
||||||
render_html5_status,
|
|
||||||
render_html5_symbol_detail,
|
|
||||||
render_html5_symbols,
|
|
||||||
)
|
|
||||||
from api_server.html5_operations import (
|
from api_server.html5_operations import (
|
||||||
latest_html5_import_job,
|
latest_html5_import_job,
|
||||||
)
|
)
|
||||||
@@ -1643,25 +1638,34 @@ async def api_health() -> dict[str, str]:
|
|||||||
|
|
||||||
@app.get("/html5")
|
@app.get("/html5")
|
||||||
async def html5_index() -> Response:
|
async def html5_index() -> Response:
|
||||||
return _html5_response(render_html5_index(_project_summaries()))
|
return _html5_response(_html5_index_page(_project_summaries()))
|
||||||
|
|
||||||
|
|
||||||
@app.post("/html5/projects")
|
@app.post("/html5/projects")
|
||||||
async def html5_create_project(request: Request) -> Response:
|
async def html5_create_project(request: Request) -> Response:
|
||||||
form = await _html5_form_data(request)
|
form = await _html5_form_data(request)
|
||||||
project_id = _form_value(form, "project_id")
|
return _html5_response(
|
||||||
if not project_id:
|
await _html5_create_project_rows(
|
||||||
raise HTTPException(status_code=400, detail="project_id is required.")
|
form=form,
|
||||||
await create_project(ProjectCreateRequest(project_id=project_id, name=_form_value(form, "name")))
|
create_project=create_project,
|
||||||
return _html5_response(render_html5_project_rows(_project_summaries()))
|
create_request=ProjectCreateRequest,
|
||||||
|
project_summaries=_project_summaries,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/html5/projects/{project_id}/delete")
|
@app.post("/html5/projects/{project_id}/delete")
|
||||||
async def html5_delete_project(project_id: str, request: Request) -> Response:
|
async def html5_delete_project(project_id: str, request: Request) -> Response:
|
||||||
form = await _html5_form_data(request)
|
form = await _html5_form_data(request)
|
||||||
confirmation = _form_value(form, "confirmation") or ""
|
return _html5_response(
|
||||||
await delete_project(project_id, ProjectDeleteRequest(confirmation=confirmation))
|
await _html5_delete_project_rows(
|
||||||
return _html5_response(render_html5_project_rows(_project_summaries()))
|
project_id=project_id,
|
||||||
|
form=form,
|
||||||
|
delete_project=delete_project,
|
||||||
|
delete_request=ProjectDeleteRequest,
|
||||||
|
project_summaries=_project_summaries,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/operations")
|
@app.get("/html5/operations")
|
||||||
@@ -1751,92 +1755,54 @@ async def html5_project_form_editor(project_id: str, form: str | None = None) ->
|
|||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/events")
|
@app.get("/html5/projects/{project_id}/events")
|
||||||
async def html5_project_events(project_id: str, once: bool = False) -> StreamingResponse:
|
async def html5_project_events(project_id: str, once: bool = False) -> StreamingResponse:
|
||||||
async def stream_status():
|
return _html5_sse_response(
|
||||||
last_fragments: dict[str, str] = {}
|
_html5_project_event_stream(
|
||||||
while True:
|
project_id=project_id,
|
||||||
yield _html5_sse_comment(f"project {project_id} heartbeat")
|
project_snapshot=_project_snapshot_or_404,
|
||||||
try:
|
project_report=project_report,
|
||||||
snapshot = _project_snapshot_or_404(project_id)
|
review=get_review,
|
||||||
fragment = render_html5_status(project_id, snapshot)
|
flowchart=project_flowchart,
|
||||||
report = await project_report(project_id)
|
authoring_changes=_authoring_change_summaries,
|
||||||
findings = await get_review(project_id)
|
once=once,
|
||||||
flowchart = await project_flowchart(project_id, focus=None, depth=1, limit=80)
|
)
|
||||||
except HTTPException as error:
|
)
|
||||||
fragment = f'<span>project: {project_id}</span><span>error: {error.detail}</span>'
|
|
||||||
report = None
|
|
||||||
findings = None
|
|
||||||
flowchart = None
|
|
||||||
for event_text in _html5_sse_if_changed(last_fragments, "status", fragment):
|
|
||||||
yield event_text
|
|
||||||
for event_text in _html5_sse_if_changed(
|
|
||||||
last_fragments,
|
|
||||||
"authoring-changes",
|
|
||||||
render_html5_authoring_changes(project_id, _authoring_change_summaries(project_id)),
|
|
||||||
):
|
|
||||||
yield event_text
|
|
||||||
if report is not None:
|
|
||||||
for event_text in _html5_sse_if_changed(last_fragments, "project-report", render_html5_project_report(project_id, report)):
|
|
||||||
yield event_text
|
|
||||||
if findings is not None:
|
|
||||||
for event_text in _html5_sse_if_changed(last_fragments, "project-review", render_html5_review(project_id, findings)):
|
|
||||||
yield event_text
|
|
||||||
if flowchart is not None:
|
|
||||||
for event_text in _html5_sse_if_changed(last_fragments, "project-flowchart", render_html5_flowchart(project_id, flowchart)):
|
|
||||||
yield event_text
|
|
||||||
if once:
|
|
||||||
break
|
|
||||||
await asyncio.sleep(5)
|
|
||||||
|
|
||||||
return _html5_sse_response(stream_status())
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/symbols")
|
@app.get("/html5/projects/{project_id}/symbols")
|
||||||
async def html5_project_symbols(project_id: str, q: str = "") -> Response:
|
async def html5_project_symbols(project_id: str, q: str = "") -> Response:
|
||||||
snapshot = _project_snapshot_or_404(project_id)
|
snapshot = _project_snapshot_or_404(project_id)
|
||||||
return _html5_response(render_html5_symbols(snapshot, q, project_id))
|
return _html5_response(_html5_project_symbols(snapshot=snapshot, q=q, project_id=project_id))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/symbols/{lineage_id}/detail")
|
@app.get("/html5/projects/{project_id}/symbols/{lineage_id}/detail")
|
||||||
async def html5_project_symbol_detail(project_id: str, lineage_id: str) -> Response:
|
async def html5_project_symbol_detail(project_id: str, lineage_id: str) -> Response:
|
||||||
references = await project_symbol_references(project_id, lineage_id, direction="both")
|
references = await project_symbol_references(project_id, lineage_id, direction="both")
|
||||||
return _html5_response(render_html5_symbol_detail(project_id, references))
|
return _html5_response(_html5_project_symbol_detail(project_id=project_id, references=references))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/source/by-path")
|
@app.get("/html5/projects/{project_id}/source/by-path")
|
||||||
async def html5_project_source_by_path(project_id: str, path: str) -> Response:
|
async def html5_project_source_by_path(project_id: str, path: str) -> Response:
|
||||||
snapshot = _project_snapshot_or_404(project_id)
|
snapshot = _project_snapshot_or_404(project_id)
|
||||||
node = next(
|
return _html5_response(_html5_project_source_by_path(snapshot=snapshot, path=path))
|
||||||
(
|
|
||||||
item
|
|
||||||
for item in snapshot.nodes
|
|
||||||
if item.source_ref is not None and item.source_ref.source_path == path
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if node is None:
|
|
||||||
raise HTTPException(status_code=404, detail=f"Source not found: {path}")
|
|
||||||
return _html5_response(render_html5_source(node))
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/source/{lineage_id}")
|
@app.get("/html5/projects/{project_id}/source/{lineage_id}")
|
||||||
async def html5_project_source(project_id: str, lineage_id: str) -> Response:
|
async def html5_project_source(project_id: str, lineage_id: str) -> Response:
|
||||||
snapshot = _project_snapshot_or_404(project_id)
|
snapshot = _project_snapshot_or_404(project_id)
|
||||||
node = _find_snapshot_node(snapshot, lineage_id)
|
node = _find_snapshot_node(snapshot, lineage_id)
|
||||||
if node is None:
|
return _html5_response(_html5_project_source_by_lineage(node=node, lineage_id=lineage_id))
|
||||||
raise HTTPException(status_code=404, detail=f"Lineage not found: {lineage_id}")
|
|
||||||
return _html5_response(render_html5_source(node))
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/report")
|
@app.get("/html5/projects/{project_id}/report")
|
||||||
async def html5_project_report(project_id: str) -> Response:
|
async def html5_project_report(project_id: str) -> Response:
|
||||||
report = await project_report(project_id)
|
report = await project_report(project_id)
|
||||||
return _html5_response(render_html5_project_report(project_id, report))
|
return _html5_response(_html5_project_report_fragment(project_id=project_id, report=report))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/review")
|
@app.get("/html5/projects/{project_id}/review")
|
||||||
async def html5_project_review(project_id: str) -> Response:
|
async def html5_project_review(project_id: str) -> Response:
|
||||||
findings = await get_review(project_id)
|
findings = await get_review(project_id)
|
||||||
return _html5_response(render_html5_review(project_id, findings))
|
return _html5_response(_html5_project_review_fragment(project_id=project_id, findings=findings))
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/flowchart")
|
@app.get("/html5/projects/{project_id}/flowchart")
|
||||||
@@ -1847,9 +1813,7 @@ async def html5_project_flowchart(
|
|||||||
limit: int = 80,
|
limit: int = 80,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
flowchart = await project_flowchart(project_id, focus=focus, depth=depth, limit=limit)
|
flowchart = await project_flowchart(project_id, focus=focus, depth=depth, limit=limit)
|
||||||
return _html5_response(
|
return _html5_response(_html5_project_flowchart_fragment(project_id=project_id, flowchart=flowchart, focus=focus, depth=depth))
|
||||||
render_html5_flowchart(project_id, flowchart, focus=focus, depth=depth),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/html5/projects/{project_id}/objects/context/{object_name}")
|
@app.get("/html5/projects/{project_id}/objects/context/{object_name}")
|
||||||
|
|||||||
Reference in New Issue
Block a user