← 피드로
Posted on Aug 12 • Edited on Aug 13
!–
Disable liquid parsing on this codeblock to prevent errors reading ‘{{‘
See: https://talk.jekyllrb.com/t/code-block-is-improperly-handled-and-generates-liquid-syntax-error/7599/2
–>
{% raw %}
import {
AlertTriangle,
ArrowDownToLine,
CheckCircle2,
ChevronRight,
Code2,
Database,
GitBranch,
ListTree,
ShieldCheck,
Sparkles,
ThumbsDown,
ThumbsUp,
} from "lucide-react";
import { useMemo, useState } from "react";
import { api, field, type ApiItem, type JsonObject } from "../lib/api";
type ResultTab =
| "overview"
| "flow"
| "procedures"
| "data"
| "dependencies"
| "evidence"
| "raw";
const tabs: Array<{ id: ResultTab; label: string }> = [
{ id: "overview", label: "Overview" },
{ id: "flow", label: "Execution flow" },
{ id: "procedures", label: "Subprograms" },
{ id: "data", label: "Data operations" },
{ id: "dependencies", label: "Dependencies" },
{ id: "evidence", label: "Evidence" },
{ id: "raw", label: "Raw metadata" },
];
function asObject(value: unknown): ApiItem {
return value && typeof value === "object" && !Array.isArray(value)
? (value as ApiItem)
: {};
}
function asArray(value: unknown): ApiItem[] {
return Array.isArray(value) ? value.map(asObject) : [];
}
function asStrings(value: unknown): string[] {
return Array.isArray(value) ? value.map(String) : [];
}
function download(filename: string, content: string, mime: string) {
const blob = new Blob([content], { type: mime });
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
anchor.download = filename;
document.body.appendChild(anchor);
anchor.click();
anchor.remove();
URL.revokeObjectURL(url);
}
function getFinalResult(run: ApiItem | null): JsonObject | null {
if (!run) return null;
const direct = run.result ?? run.RESULT;
if (direct && typeof direct === "object" && !Array.isArray(direct)) {
return direct as JsonObject;
}
const results = (run.results ?? run.RESULTS) as unknown;
if (!Array.isArray(results)) return null;
for (let index = results.length - 1; index >= 0; index -= 1) {
const row = asObject(results[index]);
if (field(row, "RESULT_TYPE", "") === "FINAL") {
const result = row.RESULT ?? row.result;
if (result && typeof result === "object" && !Array.isArray(result)) {
return result as JsonObject;
}
}
}
return null;
}
export function ResultsPanel({
run,
runId,
isAdmin,
}: {
run: ApiItem | null;
runId: string | null;
isAdmin: boolean;
}) {
const [activeTab, setActiveTab] = useState<ResultTab>("overview");
const [feedbackState, setFeedbackState] = useState<
"idle" | "sending" | "sent"
>("idle");
const result = useMemo(() => getFinalResult(run), [run]);
if (!result) return null;
const source = asObject(result.source);
const metadata = asObject(result.metadata);
const packageResult = asObject(result.package);
const procedures = asArray(result.procedures);
const objectName = field(
metadata,
"object_name",
field(packageResult, "package_name", "PL/SQL object"),
);
const sourceLines = Number(
field(source, "total_lines", field(metadata, "total_lines", 0)),
);
const confidence = Number(field(packageResult, "confidence", 0));
const sendFeedback = async (rating: "UP" | "DOWN") => {
if (!runId || feedbackState !== "idle") return;
setFeedbackState("sending");
try {
await api.feedback(runId, rating);
setFeedbackState("sent");
} catch {
setFeedbackState("idle");
}
};
const exportJson = () =>
download(
`${objectName || "analysis"}.json`,
JSON.stringify(result, null, 2),
"application/json",
);
return (
<section className="results-shell" aria-label="Analysis results">
<div className="results-heading">
<div>
<span className="eyebrow">
<Sparkles size={13} /> Analysis complete
</span>
<h2>{objectName}</h2>
<p>
Evidence-grounded analysis generated from the current source and
deterministic metadata.
</p>
</div>
<button className="button secondary" onClick={exportJson}>
<ArrowDownToLine size={16} />
Export JSON
</button>
</div>
<div className="metric-grid result-metrics">
<article className="metric-card">
<span>Object type</span>
<strong>{field(metadata, "object_type", "Unknown")}</strong>
<Code2 size={18} />
</article>
<article className="metric-card">
<span>Source lines</span>
<strong>{sourceLines.toLocaleString()}</strong>
<ListTree size={18} />
</article>
<article className="metric-card">
<span>Subprograms</span>
<strong>{procedures.length}</strong>
<GitBranch size={18} />
</article>
<article className="metric-card">
<span>Confidence</span>
<strong>{Math.round(confidence * 100)}%</strong>
<ShieldCheck size={18} />
</article>
</div>
<div className="tab-strip" role="tablist" aria-label="Result sections">
{tabs.map((tab) => (
<button
key={tab.id}
role="tab"
aria-selected={activeTab === tab.id}
className={activeTab === tab.id ? "active" : ""}
onClick={() => setActiveTab(tab.id)}
>
{tab.label}
</button>
))}
</div>
<div className="result-content">
{activeTab === "overview" && (
<Overview result={result} packageResult={packageResult} />
)}
{activeTab === "flow" && <Flow packageResult={packageResult} />}
{activeTab === "procedures" && (
<Procedures procedures={procedures} />
)}
{activeTab === "data" && (
<DataOperations metadata={metadata} procedures={procedures} />
)}
{activeTab === "dependencies" && (
<Dependencies packageResult={packageResult} />
)}
{activeTab === "evidence" && (
<Evidence result={result} source={source} />
)}
{activeTab === "raw" && (
<pre className="json-view">
<code>{JSON.stringify(metadata, null, 2)}</code>
</pre>
)}
</div>
{isAdmin ? (
<footer className="result-footer">
<div>
<strong>Was this analysis useful?</strong>
<span>
Approved feedback improves future retrieval. Negative feedback is
never promoted.
</span>
</div>
<div className="feedback-actions">
<button
className="icon-text-button"
disabled={!runId || feedbackState !== "idle"}
onClick={() => void sendFeedback("UP")}
>
<ThumbsUp size={16} /> Useful
</button>
<button
className="icon-text-button"
disabled={!runId || feedbackState !== "idle"}
onClick={() => void sendFeedback("DOWN")}
>
<ThumbsDown size={16} /> Needs work
</button>
{feedbackState === "sent" && (
<span className="feedback-sent">
<CheckCircle2 size={15} /> Recorded
</span>
)}
</div>
</footer>
) : (
<footer className="result-footer transient-result-footer">
<ShieldCheck size={20} />
<div>
<strong>Private, transient result</strong>
<span>
This analysis was returned to you without changing shared
database knowledge.
</span>
</div>
</footer>
)}
</section>
);
}
function Overview({
result,
packageResult,
}: {
result: JsonObject;
packageResult: ApiItem;
}) {
const warnings = [
...asStrings(result.warnings),
...asStrings(packageResult.risks),
...asStrings(packageResult.unknowns),
];
const entries = asStrings(packageResult.entry_points);
return (
<div className="result-two-column">
<article className="content-card span-two">
<div className="content-card-title">
<Sparkles size={17} />
<h3>Purpose and behavior</h3>
</div>
<p className="lead-copy">
{field(
packageResult,
"purpose",
field(packageResult, "overview", "No package overview is available."),
)}
</p>
{field(packageResult, "functional_summary", "") && (
<>
<h4>Functional summary</h4>
<p>{field(packageResult, "functional_summary", "")}</p>
</>
)}
</article>
<article className="content-card">
<div className="content-card-title">
<GitBranch size={17} />
<h3>Entry points</h3>
</div>
{entries.length ? (
<ul className="clean-list">
{entries.map((entry) => (
<li key={entry}>
<ChevronRight size={14} />
<code>{entry}</code>
</li>
))}
</ul>
) : (
<p className="empty-copy">No public entry points were reported.</p>
)}
</article>
<article className="content-card">
<div className="content-card-title">
<AlertTriangle size={17} />
<h3>Warnings and uncertainty</h3>
</div>
{warnings.length ? (
<ul className="warning-list">
{warnings.map((warning, index) => (
<li key={`${warning}-${index}`}>{warning}</li>
))}
</ul>
) : (
<div className="positive-state">
<CheckCircle2 size={18} />
No material warnings were reported.
</div>
)}
</article>
</div>
);
}
function Flow({ packageResult }: { packageResult: ApiItem }) {
const workflow = asStrings(packageResult.workflow);
return workflow.length ? (
<ol className="execution-flow">
{workflow.map((step, index) => (
<li key={`${step}-${index}`}>
<span>{String(index + 1).padStart(2, "0")}</span>
<div>
<strong>{step}</strong>
</div>
</li>
))}
</ol>
) : (
<EmptyResult text="No merged execution workflow was returned." />
);
}
function Procedures({ procedures }: { procedures: ApiItem[] }) {
if (!procedures.length) {
return <EmptyResult text="No procedures or functions were returned." />;
}
return (
<div className="procedure-list">
{procedures.map((procedure, index) => {
const lineRange = Array.isArray(procedure.line_range)
? procedure.line_range
: [0, 0];
const audit = field(procedure, "audit_status", "UNKNOWN");
const confidence = Number(field(procedure, "confidence", 0));
return (
<details className="procedure-card" key={`${field(procedure, "subprogram_name", "item")}-${index}`}>
<summary>
<div className="procedure-identity">
<span className="procedure-icon">
<Code2 size={17} />
</span>
<div>
<strong>
{field(procedure, "subprogram_name", `Subprogram ${index + 1}`)}
</strong>
<span>
{field(procedure, "subprogram_type", "PL/SQL")} · lines{" "}
{String(lineRange[0])}–{String(lineRange[1])}
</span>
</div>
</div>
<div className="procedure-badges">
<span className={`status-chip ${audit === "PASS" ? "good" : "warn"}`}>
{audit}
</span>
<span className="status-chip neutral">
{Math.round(confidence * 100)}%
</span>
</div>
</summary>
<div className="procedure-body">
<h4>Purpose</h4>
<p>{field(procedure, "purpose", "No purpose was returned.")}</p>
<h4>Ordered steps</h4>
<ol>
{asStrings(procedure.ordered_steps).map((step) => (
<li key={step}>{step}</li>
))}
</ol>
<div className="summary-pair">
<div>
<h4>Functional summary</h4>
<p>{field(procedure, "functional_summary", "Not available.")}</p>
</div>
<div>
<h4>Technical summary</h4>
<p>{field(procedure, "technical_summary", "Not available.")}</p>
</div>
</div>
</div>
</details>
);
})}
</div>
);
}
function DataOperations({
metadata,
procedures,
}: {
metadata: ApiItem;
procedures: ApiItem[];
}) {
const rows: Array<ApiItem & { subprogram?: string }> = [];
procedures.forEach((procedure) => {
asArray(procedure.tables).forEach((table) =>
rows.push({
...table,
subprogram: field(procedure, "subprogram_name", "—"),
}),
);
});
if (!rows.length) {
asArray(metadata.tables).forEach((table) => rows.push(table));
}
if (!rows.length) {
return <EmptyResult text="No table or view operations were extracted." />;
}
return (
<div className="data-table-wrap">
<table className="data-table">
<thead>
<tr>
<th>Subprogram</th>
<th>Object</th>
<th>Operation</th>
<th>Alias</th>
<th>Dynamic SQL</th>
<th>Evidence</th>
</tr>
</thead>
<tbody>
{rows.map((row, index) => {
const evidence = asArray(row.evidence);
return (
<tr key={`${field(row, "name", "table")}-${index}`}>
<td>{String(row.subprogram ?? "—")}</td>
<td>
<span className="table-object">
<Database size={14} />
{field(row, "owner", "") &&
`${field(row, "owner", "")}.`}
{field(row, "name", "Unknown")}
</span>
</td>
<td>
<span className="operation-chip">
{field(row, "operation", "READ")}
</span>
</td>
<td>{field(row, "alias", "—")}</td>
<td>{field(row, "dynamic_sql", false) ? "Yes" : "No"}</td>
<td>
{evidence.length
? `Line ${field(evidence[0], "start_line", "—")}`
: "—"}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
);
}
function Dependencies({ packageResult }: { packageResult: ApiItem }) {
const dependencyFlow = asStrings(packageResult.dependency_flow);
const relationships = asStrings(packageResult.procedure_relationships);
const items = [...dependencyFlow, ...relationships];
return items.length ? (
<div className="dependency-result-grid">
{items.map((item, index) => (
<article key={`${item}-${index}`}>
<span className="dependency-node">
<GitBranch size={16} />
</span>
<p>{item}</p>
</article>
))}
</div>
) : (
<EmptyResult text="No package dependencies were returned." />
);
}
function Evidence({
result,
source,
}: {
result: JsonObject;
source: ApiItem;
}) {
const retrieval = asArray(result.retrieval);
return (
<div className="result-two-column">
<article className="content-card">
<div className="content-card-title">
<ShieldCheck size={17} />
<h3>Source integrity</h3>
</div>
<dl className="definition-list">
<div>
<dt>SHA-256</dt>
<dd>
<code>{field(source, "sha256", "Not available")}</code>
</dd>
</div>
<div>
<dt>Source origin</dt>
<dd>{field(source, "origin", "Current submission")}</dd>
</div>
</dl>
</article>
<article className="content-card">
<div className="content-card-title">
<Database size={17} />
<h3>Retrieved context</h3>
</div>
{retrieval.length ? (
<ul className="clean-list stacked">
{retrieval.map((item, index) => (
<li key={`${field(item, "content_type", "context")}-${index}`}>
<div>
<strong>{field(item, "content_type", "Context")}</strong>
<span>{field(item, "review_status", "UNREVIEWED")}</span>
</div>
<span className="score">
{Math.round(Number(field(item, "score", 0)) * 100)}%
</span>
</li>
))}
</ul>
) : (
<p className="empty-copy">
No prior context exceeded the retrieval threshold.
</p>
)}
</article>
</div>
);
}
function EmptyResult({ text }: { text: string }) {
return (
<div className="empty-result">
<ListTree size={24} />
<p>{text}</p>
</div>
);
}
Enter fullscreen mode Exit fullscreen mode
추출 본문 · 출처: dev.to · https://dev.to/iamry02/resultspanel-3ed6
답글 남기기
댓글을 달기 위해서는 로그인해야합니다.