Add map empty-state dataset actions
This commit is contained in:
@@ -711,3 +711,11 @@ Added:
|
|||||||
- Added source/CRS columns to the dataset inventory section and clearer "Dataset inventory", "QA/QC evidence" and "Artifact history" report headings.
|
- Added source/CRS columns to the dataset inventory section and clearer "Dataset inventory", "QA/QC evidence" and "Artifact history" report headings.
|
||||||
- Added regression coverage for report layout markers, print CSS and HTML escaping.
|
- Added regression coverage for report layout markers, print CSS and HTML escaping.
|
||||||
- No API contracts, migrations, product capabilities, PDF/report-designer functionality, live provider fetching or AI/model dependency changes were introduced.
|
- No API contracts, migrations, product capabilities, PDF/report-designer functionality, live provider fetching or AI/model dependency changes were introduced.
|
||||||
|
|
||||||
|
## Sprint 67 map empty-state quick actions (2026-06-19)
|
||||||
|
|
||||||
|
- Added ready vector/GeoJSON dataset quick actions to the Map workspace empty state.
|
||||||
|
- Reused the existing `openDatasetInMap` flow so selecting a quick action loads the persisted dataset layer without changing API contracts.
|
||||||
|
- Added responsive styling and regression coverage for the Map quick-action grid.
|
||||||
|
- Verified locally against the live demo state that the empty map state exposes two dataset actions and opens `demo_predicted_buildings.geojson` as a 2-feature map layer.
|
||||||
|
- No API contracts, migrations, product capabilities, live provider fetching or AI/model dependency changes were introduced.
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_map_empty_state_surfaces_ready_vector_dataset_actions() -> None:
|
||||||
|
map_workspace = (ROOT / "frontend" / "src" / "components" / "map" / "MapWorkspace.tsx").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "availableMapDatasets" in map_workspace
|
||||||
|
assert "onOpenDatasetInMap" in map_workspace
|
||||||
|
assert "map-empty-action-grid" in map_workspace
|
||||||
|
assert "Open a ready vector dataset" in map_workspace
|
||||||
|
assert "Open in map" in map_workspace
|
||||||
|
assert "No ready vector datasets available yet" in map_workspace
|
||||||
|
|
||||||
|
|
||||||
|
def test_app_passes_available_vector_datasets_to_map_workspace() -> None:
|
||||||
|
app = (ROOT / "frontend" / "src" / "App.tsx").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "availableMapDatasets" in app
|
||||||
|
assert "datasets.filter((dataset) => isVectorDatasetType(dataset.dataset_type) && dataset.status === 'ready')" in app
|
||||||
|
assert "availableMapDatasets={availableMapDatasets}" in app
|
||||||
|
assert "onOpenDatasetInMap={openDatasetInMap}" in app
|
||||||
|
|
||||||
|
|
||||||
|
def test_map_empty_action_grid_has_responsive_styles() -> None:
|
||||||
|
css = (ROOT / "frontend" / "src" / "styles" / "app.css").read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert ".map-empty-action-grid" in css
|
||||||
|
assert "repeat(auto-fit, minmax(11rem, 1fr))" in css
|
||||||
@@ -2545,3 +2545,29 @@ Limitations:
|
|||||||
|
|
||||||
Next recommended pass:
|
Next recommended pass:
|
||||||
- Continue with populated Map/Data interaction polish, especially making it easier to activate the demo vector layer from the map empty state.
|
- Continue with populated Map/Data interaction polish, especially making it easier to activate the demo vector layer from the map empty state.
|
||||||
|
|
||||||
|
## Sprint 67 Map empty-state quick actions (2026-06-19)
|
||||||
|
|
||||||
|
Changed:
|
||||||
|
- Added ready vector/GeoJSON dataset quick actions to the Map workspace empty state.
|
||||||
|
- Reused the existing `openDatasetInMap` frontend flow so quick actions load the same persisted dataset layer as the Data workspace button.
|
||||||
|
- Added width-aware `.map-empty-action-grid` styling for desktop and mobile.
|
||||||
|
- Added `backend/tests/test_sprint67_map_empty_state_quick_actions.py`.
|
||||||
|
- Updated `frontend/README.md`, `docs/TODO.md` and `CHANGELOG.md`.
|
||||||
|
|
||||||
|
Tested:
|
||||||
|
- Red step: `python -m pytest backend/tests/test_sprint67_map_empty_state_quick_actions.py -q` failed on missing Map quick-action props, App wiring and CSS.
|
||||||
|
- `python -m pytest backend/tests/test_sprint67_map_empty_state_quick_actions.py backend/tests/test_sprint63_map_overlay_ergonomics.py backend/tests/test_sprint30_workbench_components.py -q` (`9 passed`)
|
||||||
|
- `cd frontend && npm run typecheck`
|
||||||
|
- `cd frontend && npm run build`
|
||||||
|
- Local Chrome/Playwright check against `http://127.0.0.1:5175` with live API proxy: Map empty state showed 2 ready dataset actions, clicking the first loaded `demo_predicted_buildings.geojson` as a 2-feature active layer, no console warnings/errors and no horizontal overflow.
|
||||||
|
- `bash scripts/run_readiness_check.sh` (`240 passed`)
|
||||||
|
|
||||||
|
Open:
|
||||||
|
- Deploy Tower and verify the live Map quick action after deployment.
|
||||||
|
|
||||||
|
Limitations:
|
||||||
|
- This is a frontend interaction polish pass only. It does not add map layers, backend endpoints, migrations, provider fetching or geospatial processing.
|
||||||
|
|
||||||
|
Next recommended pass:
|
||||||
|
- Continue with Data catalog density polish, especially making selected/reference/candidate dataset roles easier to scan in populated demo projects.
|
||||||
|
|||||||
@@ -338,3 +338,4 @@ This file now starts with the current implementation status. Older preparation/b
|
|||||||
- [x] Add map/result overlay ergonomics for active layer provenance and feature property summaries.
|
- [x] Add map/result overlay ergonomics for active layer provenance and feature property summaries.
|
||||||
- [x] Add export/report handoff polish for artifact readiness, action grouping and export provenance.
|
- [x] Add export/report handoff polish for artifact readiness, action grouping and export provenance.
|
||||||
- [x] Polish lightweight HTML project report readability, print styling and handoff sections.
|
- [x] Polish lightweight HTML project report readability, print styling and handoff sections.
|
||||||
|
- [x] Add Map empty-state quick actions for ready vector datasets.
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ The workbench now uses a task-based shell instead of a single long panel stack.
|
|||||||
- Stable navigation test anchors use `data-testid="workspace-nav-{workspace}"`.
|
- Stable navigation test anchors use `data-testid="workspace-nav-{workspace}"`.
|
||||||
- Mobile workbench navigation uses horizontal rails for the primary nav and command bar, avoiding a tall menu stack before the active workspace content.
|
- Mobile workbench navigation uses horizontal rails for the primary nav and command bar, avoiding a tall menu stack before the active workspace content.
|
||||||
- The Map workspace shows active layer source/provenance/draw-state context and selected-feature property chips before the raw JSON inspector.
|
- The Map workspace shows active layer source/provenance/draw-state context and selected-feature property chips before the raw JSON inspector.
|
||||||
|
- When the Map workspace has no active result layer, it lists ready vector/GeoJSON datasets as direct quick actions so populated demo projects can jump straight from the empty state to map inspection.
|
||||||
|
|
||||||
## Raster dependency visibility
|
## Raster dependency visibility
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,10 @@ function App(): JSX.Element {
|
|||||||
[datasets, selectedDatasetId],
|
[datasets, selectedDatasetId],
|
||||||
)
|
)
|
||||||
const availableVectorDatasets = useMemo(() => datasets.filter((item) => isVectorDatasetType(item.dataset_type)), [datasets])
|
const availableVectorDatasets = useMemo(() => datasets.filter((item) => isVectorDatasetType(item.dataset_type)), [datasets])
|
||||||
|
const availableMapDatasets = useMemo(
|
||||||
|
() => datasets.filter((dataset) => isVectorDatasetType(dataset.dataset_type) && dataset.status === 'ready'),
|
||||||
|
[datasets],
|
||||||
|
)
|
||||||
const referenceDatasets = useMemo(
|
const referenceDatasets = useMemo(
|
||||||
() => availableVectorDatasets.filter((item) => item.dataset_role === 'reference'),
|
() => availableVectorDatasets.filter((item) => item.dataset_role === 'reference'),
|
||||||
[availableVectorDatasets],
|
[availableVectorDatasets],
|
||||||
@@ -614,7 +618,9 @@ function App(): JSX.Element {
|
|||||||
mapFeatureCount={mapFeatureCount}
|
mapFeatureCount={mapFeatureCount}
|
||||||
areaFeatureCount={areaFeatureCount}
|
areaFeatureCount={areaFeatureCount}
|
||||||
selectedMapFeature={selectedMapFeature}
|
selectedMapFeature={selectedMapFeature}
|
||||||
|
availableMapDatasets={availableMapDatasets}
|
||||||
onSelectMapArea={setSelectedMapAreaId}
|
onSelectMapArea={setSelectedMapAreaId}
|
||||||
|
onOpenDatasetInMap={openDatasetInMap}
|
||||||
onSetAreaLayerVisible={setAreaLayerVisible}
|
onSetAreaLayerVisible={setAreaLayerVisible}
|
||||||
onSetAreaLayerOpacity={setAreaLayerOpacity}
|
onSetAreaLayerOpacity={setAreaLayerOpacity}
|
||||||
onSetMapLayerVisible={setMapLayerVisible}
|
onSetMapLayerVisible={setMapLayerVisible}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import GeoMap from '../GeoMap'
|
import GeoMap from '../GeoMap'
|
||||||
import type { AreaRead } from '../../types'
|
import type { AreaRead, DatasetCreateResponse } from '../../types'
|
||||||
|
|
||||||
interface MapWorkspaceProps {
|
interface MapWorkspaceProps {
|
||||||
areas: AreaRead[]
|
areas: AreaRead[]
|
||||||
@@ -16,7 +16,9 @@ interface MapWorkspaceProps {
|
|||||||
mapFeatureCount: number
|
mapFeatureCount: number
|
||||||
areaFeatureCount: number
|
areaFeatureCount: number
|
||||||
selectedMapFeature: GeoJSON.Feature | null
|
selectedMapFeature: GeoJSON.Feature | null
|
||||||
|
availableMapDatasets: DatasetCreateResponse[]
|
||||||
onSelectMapArea: (areaId: string) => void
|
onSelectMapArea: (areaId: string) => void
|
||||||
|
onOpenDatasetInMap: (dataset: DatasetCreateResponse) => void
|
||||||
onSetAreaLayerVisible: (visible: boolean) => void
|
onSetAreaLayerVisible: (visible: boolean) => void
|
||||||
onSetAreaLayerOpacity: (opacity: number) => void
|
onSetAreaLayerOpacity: (opacity: number) => void
|
||||||
onSetMapLayerVisible: (visible: boolean) => void
|
onSetMapLayerVisible: (visible: boolean) => void
|
||||||
@@ -39,7 +41,9 @@ export function MapWorkspace({
|
|||||||
mapFeatureCount,
|
mapFeatureCount,
|
||||||
areaFeatureCount,
|
areaFeatureCount,
|
||||||
selectedMapFeature,
|
selectedMapFeature,
|
||||||
|
availableMapDatasets,
|
||||||
onSelectMapArea,
|
onSelectMapArea,
|
||||||
|
onOpenDatasetInMap,
|
||||||
onSetAreaLayerVisible,
|
onSetAreaLayerVisible,
|
||||||
onSetAreaLayerOpacity,
|
onSetAreaLayerOpacity,
|
||||||
onSetMapLayerVisible,
|
onSetMapLayerVisible,
|
||||||
@@ -150,6 +154,21 @@ export function MapWorkspace({
|
|||||||
<div className="empty-state map-empty-state">
|
<div className="empty-state map-empty-state">
|
||||||
<strong>No active vector or result layer</strong>
|
<strong>No active vector or result layer</strong>
|
||||||
<p>Open a dataset, detection run, segmentation run or change result to draw it here.</p>
|
<p>Open a dataset, detection run, segmentation run or change result to draw it here.</p>
|
||||||
|
{availableMapDatasets.length > 0 ? (
|
||||||
|
<>
|
||||||
|
<p className="eyebrow">Open a ready vector dataset</p>
|
||||||
|
<div className="map-empty-action-grid">
|
||||||
|
{availableMapDatasets.map((dataset) => (
|
||||||
|
<button className="secondary-action" key={dataset.id} type="button" onClick={() => onOpenDatasetInMap(dataset)}>
|
||||||
|
<span>{dataset.name}</span>
|
||||||
|
<strong>Open in map</strong>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<p className="muted">No ready vector datasets available yet. Upload or seed a vector dataset first.</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
<GeoMap
|
<GeoMap
|
||||||
|
|||||||
@@ -1190,6 +1190,34 @@ button.entity-card {
|
|||||||
margin-bottom: 0.85rem;
|
margin-bottom: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-empty-action-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
|
||||||
|
gap: 0.55rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-empty-action-grid button {
|
||||||
|
display: grid;
|
||||||
|
min-height: 4.2rem;
|
||||||
|
gap: 0.18rem;
|
||||||
|
margin-top: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-empty-action-grid span,
|
||||||
|
.map-empty-action-grid strong {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-empty-action-grid span {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
.export-action-grid {
|
.export-action-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
|||||||
Reference in New Issue
Block a user