Apply ZIP update: 100-didactopus-full-pack-validation-update.zip [2026-03-14T13:18:53]
This commit is contained in:
parent
8c9776d6fe
commit
f3ea3da848
|
|
@ -1,13 +1,7 @@
|
||||||
concepts:
|
concepts:
|
||||||
- id: prior-and-posterior
|
- id: duplicate
|
||||||
title: Prior and Posterior
|
title: First
|
||||||
description: Beliefs before and after evidence.
|
description: Tiny.
|
||||||
prerequisites: []
|
- id: duplicate
|
||||||
- id: posterior-analysis
|
title: Second
|
||||||
title: Posterior Analysis
|
description: Tiny.
|
||||||
description: Beliefs before and after evidence.
|
|
||||||
prerequisites: []
|
|
||||||
- id: statistics-and-probability
|
|
||||||
title: Statistics and Probability
|
|
||||||
description: General overview.
|
|
||||||
prerequisites: []
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1,3 @@
|
||||||
display_name Imported Pack Broken YAML
|
name: broken-pack
|
||||||
|
display_name: Broken Pack
|
||||||
|
version: 0.1.0-draft
|
||||||
|
|
|
||||||
|
|
@ -1 +1,5 @@
|
||||||
projects: []
|
projects:
|
||||||
|
- id: bad-project
|
||||||
|
title: Bad Project
|
||||||
|
prerequisites:
|
||||||
|
- missing-concept
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,5 @@
|
||||||
stages:
|
stages:
|
||||||
- id: stage-1
|
- id: stage-1
|
||||||
title: Foundations
|
title: Bad Stage
|
||||||
concepts:
|
concepts:
|
||||||
- statistics-and-probability
|
- missing-concept
|
||||||
- id: stage-2
|
|
||||||
title: Advanced Inference
|
|
||||||
concepts:
|
|
||||||
- posterior-analysis
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
rubrics:
|
rubrics:
|
||||||
- id: basic-rubric
|
- id:
|
||||||
title: Basic Rubric
|
title: Broken Rubric
|
||||||
criteria:
|
criteria: invalid
|
||||||
- correctness
|
|
||||||
|
|
|
||||||
36
docs/faq.md
36
docs/faq.md
|
|
@ -1,28 +1,32 @@
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
## Why add import validation?
|
## Why add a full pack validator?
|
||||||
|
|
||||||
Because reducing startup friction does not mean hiding risk. A user still needs
|
Because import safety is not only about whether files exist. It is also about
|
||||||
a clear signal about whether a generated draft pack is structurally usable.
|
whether the pack makes sense as a Didactopus artifact set.
|
||||||
|
|
||||||
## How does this support the activation-energy goal?
|
## How does this help with the activation-energy problem?
|
||||||
|
|
||||||
It removes uncertainty from the handoff step. Users can see whether a draft pack
|
It reduces uncertainty at a crucial point. Users can see whether a generated pack
|
||||||
looks valid before committing it into a workspace.
|
is coherent enough to work with before losing momentum in manual debugging.
|
||||||
|
|
||||||
## What does the preview check do?
|
## What does it validate?
|
||||||
|
|
||||||
In this scaffold it checks:
|
In this scaffold it validates:
|
||||||
- required files
|
- required files
|
||||||
- basic YAML parsing
|
- YAML parsing
|
||||||
- key metadata presence
|
- metadata presence
|
||||||
- concept count
|
- duplicate concept ids
|
||||||
- overwrite conditions
|
- roadmap references
|
||||||
|
- project prerequisite references
|
||||||
|
- rubric structure
|
||||||
|
- weak concept entries
|
||||||
|
|
||||||
## Does preview guarantee correctness?
|
## Does validation guarantee quality?
|
||||||
|
|
||||||
No. It is a safety and structure check, not a guarantee of pedagogical quality.
|
No. It checks structural coherence, not whether the pack is the best possible
|
||||||
|
representation of a domain.
|
||||||
|
|
||||||
## Can import still overwrite an existing workspace?
|
## Where are validation results shown?
|
||||||
|
|
||||||
Yes, but only if overwrite is explicitly allowed.
|
They are included in import preview results and surfaced in the UI.
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,8 @@
|
||||||
stages:
|
stages:
|
||||||
- id: stage-1
|
- id: stage-1
|
||||||
title: Prior Beliefs
|
title: Bayes Basics
|
||||||
concepts:
|
concepts:
|
||||||
- bayes-prior
|
- bayes-prior
|
||||||
- id: stage-2
|
|
||||||
title: Posterior Updating
|
|
||||||
concepts:
|
|
||||||
- bayes-posterior
|
- bayes-posterior
|
||||||
- id: stage-3
|
checkpoint:
|
||||||
title: Model Checking
|
- Compare prior and posterior beliefs.
|
||||||
concepts:
|
|
||||||
- model-checking
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
||||||
[project]
|
[project]
|
||||||
name = "didactopus"
|
name = "didactopus"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Didactopus: import validation and safety layer"
|
description = "Didactopus: full pack validation layer"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
license = {text = "MIT"}
|
license = {text = "MIT"}
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,17 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import yaml
|
|
||||||
from .review_schema import ImportPreview
|
from .review_schema import ImportPreview
|
||||||
|
from .pack_validator import validate_pack_directory
|
||||||
REQUIRED_FILES = ["pack.yaml", "concepts.yaml"]
|
|
||||||
|
|
||||||
def preview_draft_pack_import(source_dir: str | Path, workspace_id: str, overwrite_required: bool = False) -> ImportPreview:
|
def preview_draft_pack_import(source_dir: str | Path, workspace_id: str, overwrite_required: bool = False) -> ImportPreview:
|
||||||
source = Path(source_dir)
|
result = validate_pack_directory(source_dir)
|
||||||
preview = ImportPreview(source_dir=str(source), workspace_id=workspace_id, overwrite_required=overwrite_required)
|
preview = ImportPreview(
|
||||||
|
source_dir=str(Path(source_dir)),
|
||||||
if not source.exists():
|
workspace_id=workspace_id,
|
||||||
preview.errors.append(f"Source directory does not exist: {source}")
|
overwrite_required=overwrite_required,
|
||||||
return preview
|
ok=result["ok"],
|
||||||
if not source.is_dir():
|
errors=list(result["errors"]),
|
||||||
preview.errors.append(f"Source path is not a directory: {source}")
|
warnings=list(result["warnings"]),
|
||||||
return preview
|
summary=dict(result["summary"]),
|
||||||
|
)
|
||||||
for filename in REQUIRED_FILES:
|
|
||||||
if not (source / filename).exists():
|
|
||||||
preview.errors.append(f"Missing required file: {filename}")
|
|
||||||
|
|
||||||
pack_data = {}
|
|
||||||
concepts_data = {}
|
|
||||||
if not preview.errors:
|
|
||||||
try:
|
|
||||||
pack_data = yaml.safe_load((source / "pack.yaml").read_text(encoding="utf-8")) or {}
|
|
||||||
except Exception as exc:
|
|
||||||
preview.errors.append(f"Could not parse pack.yaml: {exc}")
|
|
||||||
try:
|
|
||||||
concepts_data = yaml.safe_load((source / "concepts.yaml").read_text(encoding="utf-8")) or {}
|
|
||||||
except Exception as exc:
|
|
||||||
preview.errors.append(f"Could not parse concepts.yaml: {exc}")
|
|
||||||
|
|
||||||
if not preview.errors:
|
|
||||||
if "name" not in pack_data:
|
|
||||||
preview.warnings.append("pack.yaml has no 'name' field.")
|
|
||||||
if "display_name" not in pack_data:
|
|
||||||
preview.warnings.append("pack.yaml has no 'display_name' field.")
|
|
||||||
concepts = concepts_data.get("concepts", [])
|
|
||||||
if not isinstance(concepts, list):
|
|
||||||
preview.errors.append("concepts.yaml top-level 'concepts' is not a list.")
|
|
||||||
else:
|
|
||||||
preview.summary = {
|
|
||||||
"pack_name": pack_data.get("name", ""),
|
|
||||||
"display_name": pack_data.get("display_name", ""),
|
|
||||||
"version": pack_data.get("version", ""),
|
|
||||||
"concept_count": len(concepts),
|
|
||||||
"has_conflict_report": (source / "conflict_report.md").exists(),
|
|
||||||
"has_review_report": (source / "review_report.md").exists(),
|
|
||||||
}
|
|
||||||
if len(concepts) == 0:
|
|
||||||
preview.warnings.append("concepts.yaml contains zero concepts.")
|
|
||||||
|
|
||||||
preview.ok = len(preview.errors) == 0
|
|
||||||
return preview
|
return preview
|
||||||
|
|
|
||||||
|
|
@ -11,53 +11,33 @@ def _safe_load_yaml(path: Path, errors: list[str], label: str):
|
||||||
errors.append(f"Could not parse {label}: {exc}")
|
errors.append(f"Could not parse {label}: {exc}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def load_pack_artifacts(source_dir: str | Path) -> dict:
|
def validate_pack_directory(source_dir: str | Path) -> dict:
|
||||||
source = Path(source_dir)
|
source = Path(source_dir)
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
|
warnings: list[str] = []
|
||||||
|
summary: dict = {}
|
||||||
|
|
||||||
if not source.exists():
|
if not source.exists():
|
||||||
return {"ok": False, "errors": [f"Source directory does not exist: {source}"], "warnings": [], "summary": {}, "artifacts": {}}
|
return {"ok": False, "errors": [f"Source directory does not exist: {source}"], "warnings": [], "summary": {}}
|
||||||
if not source.is_dir():
|
if not source.is_dir():
|
||||||
return {"ok": False, "errors": [f"Source path is not a directory: {source}"], "warnings": [], "summary": {}, "artifacts": {}}
|
return {"ok": False, "errors": [f"Source path is not a directory: {source}"], "warnings": [], "summary": {}}
|
||||||
|
|
||||||
for filename in REQUIRED_FILES:
|
for filename in REQUIRED_FILES:
|
||||||
if not (source / filename).exists():
|
if not (source / filename).exists():
|
||||||
errors.append(f"Missing required file: {filename}")
|
errors.append(f"Missing required file: {filename}")
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
return {"ok": False, "errors": errors, "warnings": [], "summary": {}, "artifacts": {}}
|
return {"ok": False, "errors": errors, "warnings": warnings, "summary": summary}
|
||||||
|
|
||||||
pack_data = _safe_load_yaml(source / "pack.yaml", errors, "pack.yaml")
|
pack_data = _safe_load_yaml(source / "pack.yaml", errors, "pack.yaml")
|
||||||
concepts_data = _safe_load_yaml(source / "concepts.yaml", errors, "concepts.yaml")
|
concepts_data = _safe_load_yaml(source / "concepts.yaml", errors, "concepts.yaml")
|
||||||
roadmap_data = _safe_load_yaml(source / "roadmap.yaml", errors, "roadmap.yaml")
|
roadmap_data = _safe_load_yaml(source / "roadmap.yaml", errors, "roadmap.yaml")
|
||||||
projects_data = _safe_load_yaml(source / "projects.yaml", errors, "projects.yaml")
|
projects_data = _safe_load_yaml(source / "projects.yaml", errors, "projects.yaml")
|
||||||
rubrics_data = _safe_load_yaml(source / "rubrics.yaml", errors, "rubrics.yaml")
|
rubrics_data = _safe_load_yaml(source / "rubrics.yaml", errors, "rubrics.yaml")
|
||||||
return {
|
|
||||||
"ok": len(errors) == 0,
|
|
||||||
"errors": errors,
|
|
||||||
"warnings": [],
|
|
||||||
"summary": {},
|
|
||||||
"artifacts": {
|
|
||||||
"pack": pack_data,
|
|
||||||
"concepts": concepts_data,
|
|
||||||
"roadmap": roadmap_data,
|
|
||||||
"projects": projects_data,
|
|
||||||
"rubrics": rubrics_data,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
def validate_pack_directory(source_dir: str | Path) -> dict:
|
if errors:
|
||||||
loaded = load_pack_artifacts(source_dir)
|
|
||||||
errors = list(loaded["errors"])
|
|
||||||
warnings = list(loaded["warnings"])
|
|
||||||
summary = dict(loaded["summary"])
|
|
||||||
if not loaded["ok"]:
|
|
||||||
return {"ok": False, "errors": errors, "warnings": warnings, "summary": summary}
|
return {"ok": False, "errors": errors, "warnings": warnings, "summary": summary}
|
||||||
|
|
||||||
pack_data = loaded["artifacts"]["pack"]
|
|
||||||
concepts_data = loaded["artifacts"]["concepts"]
|
|
||||||
roadmap_data = loaded["artifacts"]["roadmap"]
|
|
||||||
projects_data = loaded["artifacts"]["projects"]
|
|
||||||
rubrics_data = loaded["artifacts"]["rubrics"]
|
|
||||||
|
|
||||||
for field in ["name", "display_name", "version"]:
|
for field in ["name", "display_name", "version"]:
|
||||||
if field not in pack_data:
|
if field not in pack_data:
|
||||||
warnings.append(f"pack.yaml has no '{field}' field.")
|
warnings.append(f"pack.yaml has no '{field}' field.")
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ class ReviewBridgeHandler(BaseHTTPRequestHandler):
|
||||||
json_response(self, 404, {"error": "not found"})
|
json_response(self, 404, {"error": "not found"})
|
||||||
|
|
||||||
def build_parser() -> argparse.ArgumentParser:
|
def build_parser() -> argparse.ArgumentParser:
|
||||||
parser = argparse.ArgumentParser(description="Didactopus local review bridge server with import validation")
|
parser = argparse.ArgumentParser(description="Didactopus local review bridge server with full pack validation")
|
||||||
parser.add_argument("--config", default="configs/config.example.yaml")
|
parser.add_argument("--config", default="configs/config.example.yaml")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,16 @@ from pathlib import Path
|
||||||
from didactopus.import_validator import preview_draft_pack_import
|
from didactopus.import_validator import preview_draft_pack_import
|
||||||
|
|
||||||
def test_valid_preview(tmp_path: Path) -> None:
|
def test_valid_preview(tmp_path: Path) -> None:
|
||||||
src = tmp_path / "src"
|
(tmp_path / "pack.yaml").write_text("name: p\ndisplay_name: P\nversion: 0.1.0\n", encoding="utf-8")
|
||||||
src.mkdir()
|
(tmp_path / "concepts.yaml").write_text("concepts:\n - id: c1\n title: C1\n description: A full enough description.\n", encoding="utf-8")
|
||||||
(src / "pack.yaml").write_text("name: p\ndisplay_name: P\nversion: 0.1.0\n", encoding="utf-8")
|
(tmp_path / "roadmap.yaml").write_text("stages: []\n", encoding="utf-8")
|
||||||
(src / "concepts.yaml").write_text("concepts: []\n", encoding="utf-8")
|
(tmp_path / "projects.yaml").write_text("projects: []\n", encoding="utf-8")
|
||||||
preview = preview_draft_pack_import(src, "ws1")
|
(tmp_path / "rubrics.yaml").write_text("rubrics: []\n", encoding="utf-8")
|
||||||
|
preview = preview_draft_pack_import(tmp_path, "ws1")
|
||||||
assert preview.ok is True
|
assert preview.ok is True
|
||||||
assert preview.summary["concept_count"] == 0
|
assert preview.summary["concept_count"] == 1
|
||||||
|
|
||||||
def test_missing_required_file(tmp_path: Path) -> None:
|
def test_missing_required_file(tmp_path: Path) -> None:
|
||||||
src = tmp_path / "src"
|
(tmp_path / "pack.yaml").write_text("name: p\n", encoding="utf-8")
|
||||||
src.mkdir()
|
preview = preview_draft_pack_import(tmp_path, "ws1")
|
||||||
(src / "pack.yaml").write_text("name: p\n", encoding="utf-8")
|
|
||||||
preview = preview_draft_pack_import(src, "ws1")
|
|
||||||
assert preview.ok is False
|
assert preview.ok is False
|
||||||
assert any("Missing required file" in e for e in preview.errors)
|
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,13 @@ def test_valid_pack(tmp_path: Path) -> None:
|
||||||
(tmp_path / "rubrics.yaml").write_text("rubrics:\n - id: r1\n title: R1\n criteria: [correctness]\n", encoding="utf-8")
|
(tmp_path / "rubrics.yaml").write_text("rubrics:\n - id: r1\n title: R1\n criteria: [correctness]\n", encoding="utf-8")
|
||||||
result = validate_pack_directory(tmp_path)
|
result = validate_pack_directory(tmp_path)
|
||||||
assert result["ok"] is True
|
assert result["ok"] is True
|
||||||
|
|
||||||
|
def test_cross_file_errors(tmp_path: Path) -> None:
|
||||||
|
(tmp_path / "pack.yaml").write_text("name: p\ndisplay_name: P\nversion: 0.1.0\n", encoding="utf-8")
|
||||||
|
(tmp_path / "concepts.yaml").write_text("concepts:\n - id: dup\n title: A\n description: Thin.\n - id: dup\n title: B\n description: Thin.\n", encoding="utf-8")
|
||||||
|
(tmp_path / "roadmap.yaml").write_text("stages:\n - id: s1\n title: S1\n concepts: [missing]\n", encoding="utf-8")
|
||||||
|
(tmp_path / "projects.yaml").write_text("projects:\n - id: p1\n title: P1\n prerequisites: [missing]\n", encoding="utf-8")
|
||||||
|
(tmp_path / "rubrics.yaml").write_text("rubrics:\n - id: r1\n title: R1\n criteria: broken\n", encoding="utf-8")
|
||||||
|
result = validate_pack_directory(tmp_path)
|
||||||
|
assert result["ok"] is False
|
||||||
|
assert any("Duplicate concept id" in e for e in result["errors"])
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from didactopus.workspace_manager import WorkspaceManager
|
from didactopus.workspace_manager import WorkspaceManager
|
||||||
|
|
||||||
def test_import_preview_and_overwrite_warning(tmp_path: Path) -> None:
|
def make_valid_pack(src: Path):
|
||||||
src = tmp_path / "srcpack"
|
|
||||||
src.mkdir()
|
src.mkdir()
|
||||||
(src / "pack.yaml").write_text("name: x\ndisplay_name: X\nversion: 0.1.0\n", encoding="utf-8")
|
(src / "pack.yaml").write_text("name: x\ndisplay_name: X\nversion: 0.1.0\n", encoding="utf-8")
|
||||||
(src / "concepts.yaml").write_text("concepts: []\n", encoding="utf-8")
|
(src / "concepts.yaml").write_text("concepts:\n - id: c1\n title: C1\n description: A full enough description.\n", encoding="utf-8")
|
||||||
|
(src / "roadmap.yaml").write_text("stages: []\n", encoding="utf-8")
|
||||||
|
(src / "projects.yaml").write_text("projects: []\n", encoding="utf-8")
|
||||||
|
(src / "rubrics.yaml").write_text("rubrics: []\n", encoding="utf-8")
|
||||||
|
|
||||||
|
def test_import_preview_and_overwrite_warning(tmp_path: Path) -> None:
|
||||||
|
src = tmp_path / "srcpack"
|
||||||
|
make_valid_pack(src)
|
||||||
mgr = WorkspaceManager(tmp_path / "registry.json", tmp_path / "roots")
|
mgr = WorkspaceManager(tmp_path / "registry.json", tmp_path / "roots")
|
||||||
mgr.create_workspace("ws2", "Workspace Two")
|
mgr.create_workspace("ws2", "Workspace Two")
|
||||||
preview = mgr.preview_import(src, "ws2")
|
preview = mgr.preview_import(src, "ws2")
|
||||||
|
|
@ -13,9 +19,7 @@ def test_import_preview_and_overwrite_warning(tmp_path: Path) -> None:
|
||||||
|
|
||||||
def test_import_draft_pack_requires_overwrite_flag(tmp_path: Path) -> None:
|
def test_import_draft_pack_requires_overwrite_flag(tmp_path: Path) -> None:
|
||||||
src = tmp_path / "srcpack"
|
src = tmp_path / "srcpack"
|
||||||
src.mkdir()
|
make_valid_pack(src)
|
||||||
(src / "pack.yaml").write_text("name: x\ndisplay_name: X\nversion: 0.1.0\n", encoding="utf-8")
|
|
||||||
(src / "concepts.yaml").write_text("concepts: []\n", encoding="utf-8")
|
|
||||||
mgr = WorkspaceManager(tmp_path / "registry.json", tmp_path / "roots")
|
mgr = WorkspaceManager(tmp_path / "registry.json", tmp_path / "roots")
|
||||||
mgr.create_workspace("ws2", "Workspace Two")
|
mgr.create_workspace("ws2", "Workspace Two")
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -46,11 +46,7 @@ export default function App() {
|
||||||
});
|
});
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setImportPreview(data);
|
setImportPreview(data);
|
||||||
if (data.ok) {
|
setMessage(data.ok ? "Import preview ready." : "Import preview found blocking errors.");
|
||||||
setMessage("Import preview ready.");
|
|
||||||
} else {
|
|
||||||
setMessage("Import preview found blocking errors.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function importWorkspace() {
|
async function importWorkspace() {
|
||||||
|
|
@ -157,10 +153,10 @@ export default function App() {
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<header className="hero">
|
<header className="hero">
|
||||||
<div>
|
<div>
|
||||||
<h1>Didactopus Import Validation</h1>
|
<h1>Didactopus Full Pack Validation</h1>
|
||||||
<p>
|
<p>
|
||||||
Reduce the activation-energy hump from generated draft packs to curated review workspaces
|
Reduce the activation-energy hump from generated draft packs to curated review workspaces
|
||||||
by previewing structure, warnings, and overwrite risk before import.
|
by previewing structural coherence, warnings, and overwrite risk before import.
|
||||||
</p>
|
</p>
|
||||||
<div className="small">{message}</div>
|
<div className="small">{message}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -205,14 +201,17 @@ export default function App() {
|
||||||
<div><strong>Overwrite Required:</strong> {String(importPreview.overwrite_required)}</div>
|
<div><strong>Overwrite Required:</strong> {String(importPreview.overwrite_required)}</div>
|
||||||
<div><strong>Pack:</strong> {importPreview.summary?.display_name || importPreview.summary?.pack_name || "-"}</div>
|
<div><strong>Pack:</strong> {importPreview.summary?.display_name || importPreview.summary?.pack_name || "-"}</div>
|
||||||
<div><strong>Version:</strong> {importPreview.summary?.version || "-"}</div>
|
<div><strong>Version:</strong> {importPreview.summary?.version || "-"}</div>
|
||||||
<div><strong>Concept Count:</strong> {importPreview.summary?.concept_count ?? "-"}</div>
|
<div><strong>Concepts:</strong> {importPreview.summary?.concept_count ?? "-"}</div>
|
||||||
|
<div><strong>Roadmap Stages:</strong> {importPreview.summary?.roadmap_stage_count ?? "-"}</div>
|
||||||
|
<div><strong>Projects:</strong> {importPreview.summary?.project_count ?? "-"}</div>
|
||||||
|
<div><strong>Rubrics:</strong> {importPreview.summary?.rubric_count ?? "-"}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Errors</h2>
|
<h2>Validation Errors</h2>
|
||||||
<ul>{(importPreview.errors || []).length ? importPreview.errors.map((x, i) => <li key={i}>{x}</li>) : <li>none</li>}</ul>
|
<ul>{(importPreview.errors || []).length ? importPreview.errors.map((x, i) => <li key={i}>{x}</li>) : <li>none</li>}</ul>
|
||||||
</div>
|
</div>
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<h2>Warnings</h2>
|
<h2>Validation Warnings</h2>
|
||||||
<ul>{(importPreview.warnings || []).length ? importPreview.warnings.map((x, i) => <li key={i}>{x}</li>) : <li>none</li>}</ul>
|
<ul>{(importPreview.warnings || []).length ? importPreview.warnings.map((x, i) => <li key={i}>{x}</li>) : <li>none</li>}</ul>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue