feat: implement cascading delete for gateway and associated installed skills
This commit is contained in:
committed by
Abhimanyu Saharan
parent
577c0d2839
commit
da6cc2544b
@@ -14,6 +14,7 @@ from app.db import crud
|
||||
from app.db.pagination import paginate
|
||||
from app.db.session import get_session
|
||||
from app.models.agents import Agent
|
||||
from app.models.gateway_installed_skills import GatewayInstalledSkill
|
||||
from app.models.gateways import Gateway
|
||||
from app.schemas.common import OkResponse
|
||||
from app.schemas.gateways import (
|
||||
@@ -175,6 +176,12 @@ async def delete_gateway(
|
||||
await service.clear_agent_foreign_keys(agent_id=agent.id)
|
||||
await session.delete(agent)
|
||||
|
||||
installed_skills = await GatewayInstalledSkill.objects.filter_by(
|
||||
gateway_id=gateway.id,
|
||||
).all(session)
|
||||
for installed_skill in installed_skills:
|
||||
await session.delete(installed_skill)
|
||||
|
||||
await session.delete(gateway)
|
||||
await session.commit()
|
||||
return OkResponse()
|
||||
|
||||
@@ -73,6 +73,7 @@ def upgrade() -> None:
|
||||
sa.ForeignKeyConstraint(
|
||||
["gateway_id"],
|
||||
["gateways.id"],
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["skill_id"],
|
||||
|
||||
@@ -15,6 +15,7 @@ from sqlmodel import SQLModel, col, select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
from app.api.deps import require_org_admin
|
||||
from app.api.gateways import router as gateways_router
|
||||
from app.api.skills_marketplace import (
|
||||
PackSkillCandidate,
|
||||
_collect_pack_skills_from_repo,
|
||||
@@ -44,6 +45,7 @@ def _build_test_app(
|
||||
) -> FastAPI:
|
||||
app = FastAPI()
|
||||
api_v1 = APIRouter(prefix="/api/v1")
|
||||
api_v1.include_router(gateways_router)
|
||||
api_v1.include_router(skills_marketplace_router)
|
||||
app.include_router(api_v1)
|
||||
|
||||
@@ -171,6 +173,58 @@ async def test_install_skill_dispatches_instruction_and_persists_installation(
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_gateway_removes_installed_skill_rows() -> None:
|
||||
engine = await _make_engine()
|
||||
session_maker = async_sessionmaker(
|
||||
engine,
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False,
|
||||
)
|
||||
try:
|
||||
async with session_maker() as session:
|
||||
organization, gateway = await _seed_base(session)
|
||||
skill = MarketplaceSkill(
|
||||
organization_id=organization.id,
|
||||
name="Deploy Helper",
|
||||
source_url="https://example.com/skills/deploy-helper.git",
|
||||
)
|
||||
session.add(skill)
|
||||
await session.commit()
|
||||
await session.refresh(skill)
|
||||
session.add(
|
||||
GatewayInstalledSkill(
|
||||
gateway_id=gateway.id,
|
||||
skill_id=skill.id,
|
||||
),
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
app = _build_test_app(session_maker, organization=organization)
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=app),
|
||||
base_url="http://testserver",
|
||||
) as client:
|
||||
response = await client.delete(f"/api/v1/gateways/{gateway.id}")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"ok": True}
|
||||
|
||||
async with session_maker() as session:
|
||||
deleted_gateway = await session.get(Gateway, gateway.id)
|
||||
assert deleted_gateway is None
|
||||
remaining_installs = (
|
||||
await session.exec(
|
||||
select(GatewayInstalledSkill).where(
|
||||
col(GatewayInstalledSkill.gateway_id) == gateway.id,
|
||||
),
|
||||
)
|
||||
).all()
|
||||
assert remaining_installs == []
|
||||
finally:
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_marketplace_skills_marks_installed_cards() -> None:
|
||||
engine = await _make_engine()
|
||||
|
||||
@@ -7,6 +7,6 @@ type EditMarketplaceSkillPageProps = {
|
||||
export default async function EditMarketplaceSkillPage({
|
||||
params,
|
||||
}: EditMarketplaceSkillPageProps) {
|
||||
const { skillId } = await params;
|
||||
redirect(`/skills/packs/${skillId}/edit`);
|
||||
await params;
|
||||
redirect("/skills/marketplace");
|
||||
}
|
||||
|
||||
@@ -80,9 +80,9 @@ const toPackLabel = (packUrl: string): string => {
|
||||
}
|
||||
};
|
||||
|
||||
const toPackDetailHref = (packUrl: string): string => {
|
||||
const toPacksHref = (packUrl: string): string => {
|
||||
const params = new URLSearchParams({ source_url: packUrl });
|
||||
return `/skills/packs/detail?${params.toString()}`;
|
||||
return `/skills/packs?${params.toString()}`;
|
||||
};
|
||||
|
||||
export function MarketplaceSkillsTable({
|
||||
@@ -143,7 +143,7 @@ export function MarketplaceSkillsTable({
|
||||
const packUrl = toPackUrl(row.original.source_url);
|
||||
return (
|
||||
<Link
|
||||
href={toPackDetailHref(packUrl)}
|
||||
href={toPacksHref(packUrl)}
|
||||
className="inline-flex items-center gap-1 text-sm font-medium text-slate-700 hover:text-blue-600"
|
||||
>
|
||||
{truncate(toPackLabel(packUrl), 40)}
|
||||
|
||||
Reference in New Issue
Block a user