feat: add description field to boards and update related components for onboarding
This commit is contained in:
@@ -175,6 +175,7 @@ async def start_onboarding(
|
||||
prompt = (
|
||||
"BOARD ONBOARDING REQUEST\n\n"
|
||||
f"Board Name: {board.name}\n"
|
||||
f"Board Description: {board.description or '(not provided)'}\n"
|
||||
"You are the gateway agent. Ask the user 6-10 focused questions total:\n"
|
||||
"- 3-6 questions to clarify the board goal.\n"
|
||||
"- 1 question to choose a unique name for the board lead agent "
|
||||
|
||||
@@ -23,6 +23,7 @@ class Board(TenantScoped, table=True):
|
||||
organization_id: UUID = Field(foreign_key="organizations.id", index=True)
|
||||
name: str
|
||||
slug: str = Field(index=True)
|
||||
description: str = Field(default="")
|
||||
gateway_id: UUID | None = Field(default=None, foreign_key="gateways.id", index=True)
|
||||
board_group_id: UUID | None = Field(
|
||||
default=None,
|
||||
|
||||
@@ -11,6 +11,7 @@ from sqlmodel import SQLModel
|
||||
|
||||
_ERR_GOAL_FIELDS_REQUIRED = "Confirmed goal boards require objective and success_metrics"
|
||||
_ERR_GATEWAY_REQUIRED = "gateway_id is required"
|
||||
_ERR_DESCRIPTION_REQUIRED = "description is required"
|
||||
RUNTIME_ANNOTATION_TYPES = (datetime, UUID)
|
||||
|
||||
|
||||
@@ -19,6 +20,7 @@ class BoardBase(SQLModel):
|
||||
|
||||
name: str
|
||||
slug: str
|
||||
description: str
|
||||
gateway_id: UUID | None = None
|
||||
board_group_id: UUID | None = None
|
||||
board_type: str = "goal"
|
||||
@@ -37,6 +39,10 @@ class BoardCreate(BoardBase):
|
||||
@model_validator(mode="after")
|
||||
def validate_goal_fields(self) -> Self:
|
||||
"""Require gateway and goal details when creating a confirmed goal board."""
|
||||
description = self.description.strip()
|
||||
if not description:
|
||||
raise ValueError(_ERR_DESCRIPTION_REQUIRED)
|
||||
self.description = description
|
||||
if self.gateway_id is None:
|
||||
raise ValueError(_ERR_GATEWAY_REQUIRED)
|
||||
if (
|
||||
@@ -53,6 +59,7 @@ class BoardUpdate(SQLModel):
|
||||
|
||||
name: str | None = None
|
||||
slug: str | None = None
|
||||
description: str | None = None
|
||||
gateway_id: UUID | None = None
|
||||
board_group_id: UUID | None = None
|
||||
board_type: str | None = None
|
||||
@@ -68,6 +75,13 @@ class BoardUpdate(SQLModel):
|
||||
# Treat explicit null like "unset" is invalid for patch updates.
|
||||
if "gateway_id" in self.model_fields_set and self.gateway_id is None:
|
||||
raise ValueError(_ERR_GATEWAY_REQUIRED)
|
||||
if "description" in self.model_fields_set:
|
||||
if self.description is None:
|
||||
raise ValueError(_ERR_DESCRIPTION_REQUIRED)
|
||||
description = self.description.strip()
|
||||
if not description:
|
||||
raise ValueError(_ERR_DESCRIPTION_REQUIRED)
|
||||
self.description = description
|
||||
return self
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Add description field to boards.
|
||||
|
||||
Revision ID: c3b58a391f2e
|
||||
Revises: b308f2876359
|
||||
Create Date: 2026-02-11 00:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "c3b58a391f2e"
|
||||
down_revision = "b308f2876359"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Add required board description column."""
|
||||
op.add_column(
|
||||
"boards",
|
||||
sa.Column(
|
||||
"description",
|
||||
sa.String(),
|
||||
nullable=False,
|
||||
server_default="",
|
||||
),
|
||||
)
|
||||
op.alter_column("boards", "description", server_default=None)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Remove board description column."""
|
||||
op.drop_column("boards", "description")
|
||||
@@ -6,7 +6,7 @@ from uuid import uuid4
|
||||
import pytest
|
||||
|
||||
from app.schemas.board_onboarding import BoardOnboardingConfirm
|
||||
from app.schemas.boards import BoardCreate
|
||||
from app.schemas.boards import BoardCreate, BoardUpdate
|
||||
|
||||
|
||||
def test_goal_board_requires_objective_and_metrics_when_confirmed() -> None:
|
||||
@@ -18,6 +18,7 @@ def test_goal_board_requires_objective_and_metrics_when_confirmed() -> None:
|
||||
BoardCreate(
|
||||
name="Goal Board",
|
||||
slug="goal",
|
||||
description="Ship onboarding improvements.",
|
||||
gateway_id=uuid4(),
|
||||
board_type="goal",
|
||||
goal_confirmed=True,
|
||||
@@ -26,6 +27,7 @@ def test_goal_board_requires_objective_and_metrics_when_confirmed() -> None:
|
||||
BoardCreate(
|
||||
name="Goal Board",
|
||||
slug="goal",
|
||||
description="Ship onboarding improvements.",
|
||||
gateway_id=uuid4(),
|
||||
board_type="goal",
|
||||
goal_confirmed=True,
|
||||
@@ -36,7 +38,13 @@ def test_goal_board_requires_objective_and_metrics_when_confirmed() -> None:
|
||||
|
||||
def test_goal_board_allows_missing_objective_before_confirmation() -> None:
|
||||
"""Draft goal boards may omit objective/success_metrics before confirmation."""
|
||||
BoardCreate(name="Draft", slug="draft", gateway_id=uuid4(), board_type="goal")
|
||||
BoardCreate(
|
||||
name="Draft",
|
||||
slug="draft",
|
||||
description="Iterate on backlog hygiene.",
|
||||
gateway_id=uuid4(),
|
||||
board_type="goal",
|
||||
)
|
||||
|
||||
|
||||
def test_general_board_allows_missing_objective() -> None:
|
||||
@@ -44,11 +52,30 @@ def test_general_board_allows_missing_objective() -> None:
|
||||
BoardCreate(
|
||||
name="General",
|
||||
slug="general",
|
||||
description="General coordination board.",
|
||||
gateway_id=uuid4(),
|
||||
board_type="general",
|
||||
)
|
||||
|
||||
|
||||
def test_board_create_requires_description() -> None:
|
||||
"""Board creation should reject empty descriptions."""
|
||||
with pytest.raises(ValueError, match="description is required"):
|
||||
BoardCreate(
|
||||
name="Goal Board",
|
||||
slug="goal",
|
||||
description=" ",
|
||||
gateway_id=uuid4(),
|
||||
board_type="goal",
|
||||
)
|
||||
|
||||
|
||||
def test_board_update_rejects_empty_description_patch() -> None:
|
||||
"""Patch payloads should reject blank descriptions."""
|
||||
with pytest.raises(ValueError, match="description is required"):
|
||||
BoardUpdate(description=" ")
|
||||
|
||||
|
||||
def test_onboarding_confirm_requires_goal_fields() -> None:
|
||||
"""Onboarding confirm should enforce goal fields for goal board types."""
|
||||
with pytest.raises(
|
||||
|
||||
Reference in New Issue
Block a user