refactor: clean up code formatting and improve readability in various files
This commit is contained in:
@@ -318,9 +318,13 @@ async def delete_board(
|
||||
await session.execute(
|
||||
delete(BoardOnboardingSession).where(col(BoardOnboardingSession.board_id) == board.id)
|
||||
)
|
||||
await session.execute(delete(OrganizationBoardAccess).where(col(OrganizationBoardAccess.board_id) == board.id))
|
||||
await session.execute(
|
||||
delete(OrganizationInviteBoardAccess).where(col(OrganizationInviteBoardAccess.board_id) == board.id)
|
||||
delete(OrganizationBoardAccess).where(col(OrganizationBoardAccess.board_id) == board.id)
|
||||
)
|
||||
await session.execute(
|
||||
delete(OrganizationInviteBoardAccess).where(
|
||||
col(OrganizationInviteBoardAccess.board_id) == board.id
|
||||
)
|
||||
)
|
||||
|
||||
# Tasks reference agents (assigned_agent_id) and have dependents (fingerprints/dependencies), so
|
||||
|
||||
@@ -99,15 +99,21 @@ def test_role_rank_unknown_role_falls_back_to_member_rank() -> None:
|
||||
|
||||
|
||||
def test_is_org_admin_owner_admin_member() -> None:
|
||||
assert organizations.is_org_admin(OrganizationMember(organization_id=uuid4(), user_id=uuid4(), role="owner"))
|
||||
assert organizations.is_org_admin(OrganizationMember(organization_id=uuid4(), user_id=uuid4(), role="admin"))
|
||||
assert organizations.is_org_admin(
|
||||
OrganizationMember(organization_id=uuid4(), user_id=uuid4(), role="owner")
|
||||
)
|
||||
assert organizations.is_org_admin(
|
||||
OrganizationMember(organization_id=uuid4(), user_id=uuid4(), role="admin")
|
||||
)
|
||||
assert not organizations.is_org_admin(
|
||||
OrganizationMember(organization_id=uuid4(), user_id=uuid4(), role="member")
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ensure_member_for_user_returns_existing_membership(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def test_ensure_member_for_user_returns_existing_membership(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
user = User(clerk_user_id="u1")
|
||||
existing = OrganizationMember(organization_id=uuid4(), user_id=user.id, role="member")
|
||||
|
||||
@@ -122,7 +128,9 @@ async def test_ensure_member_for_user_returns_existing_membership(monkeypatch: p
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ensure_member_for_user_accepts_pending_invite(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def test_ensure_member_for_user_accepts_pending_invite(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
org_id = uuid4()
|
||||
invite = OrganizationInvite(
|
||||
organization_id=org_id,
|
||||
@@ -140,7 +148,9 @@ async def test_ensure_member_for_user_accepts_pending_invite(monkeypatch: pytest
|
||||
|
||||
accepted = OrganizationMember(organization_id=org_id, user_id=user.id, role="member")
|
||||
|
||||
async def _fake_accept(_session: Any, _invite: OrganizationInvite, _user: User) -> OrganizationMember:
|
||||
async def _fake_accept(
|
||||
_session: Any, _invite: OrganizationInvite, _user: User
|
||||
) -> OrganizationMember:
|
||||
assert _invite is invite
|
||||
assert _user is user
|
||||
return accepted
|
||||
@@ -155,7 +165,9 @@ async def test_ensure_member_for_user_accepts_pending_invite(monkeypatch: pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ensure_member_for_user_creates_default_org_and_first_owner(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def test_ensure_member_for_user_creates_default_org_and_first_owner(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
user = User(clerk_user_id="u1", email=None)
|
||||
org = Organization(id=uuid4(), name=organizations.DEFAULT_ORG_NAME)
|
||||
|
||||
@@ -186,7 +198,10 @@ async def test_has_board_access_denies_cross_org() -> None:
|
||||
session = _FakeSession(exec_results=[])
|
||||
member = OrganizationMember(organization_id=uuid4(), user_id=uuid4(), role="member")
|
||||
board = Board(id=uuid4(), organization_id=uuid4(), name="b", slug="b")
|
||||
assert await organizations.has_board_access(session, member=member, board=board, write=False) is False
|
||||
assert (
|
||||
await organizations.has_board_access(session, member=member, board=board, write=False)
|
||||
is False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -202,7 +217,10 @@ async def test_has_board_access_uses_org_board_access_row_read_and_write() -> No
|
||||
can_write=False,
|
||||
)
|
||||
session = _FakeSession(exec_results=[_FakeExecResult(first_value=access)])
|
||||
assert await organizations.has_board_access(session, member=member, board=board, write=False) is True
|
||||
assert (
|
||||
await organizations.has_board_access(session, member=member, board=board, write=False)
|
||||
is True
|
||||
)
|
||||
|
||||
access2 = OrganizationBoardAccess(
|
||||
organization_member_id=member.id,
|
||||
@@ -211,7 +229,10 @@ async def test_has_board_access_uses_org_board_access_row_read_and_write() -> No
|
||||
can_write=True,
|
||||
)
|
||||
session2 = _FakeSession(exec_results=[_FakeExecResult(first_value=access2)])
|
||||
assert await organizations.has_board_access(session2, member=member, board=board, write=False) is True
|
||||
assert (
|
||||
await organizations.has_board_access(session2, member=member, board=board, write=False)
|
||||
is True
|
||||
)
|
||||
|
||||
access3 = OrganizationBoardAccess(
|
||||
organization_member_id=member.id,
|
||||
@@ -220,7 +241,10 @@ async def test_has_board_access_uses_org_board_access_row_read_and_write() -> No
|
||||
can_write=False,
|
||||
)
|
||||
session3 = _FakeSession(exec_results=[_FakeExecResult(first_value=access3)])
|
||||
assert await organizations.has_board_access(session3, member=member, board=board, write=True) is False
|
||||
assert (
|
||||
await organizations.has_board_access(session3, member=member, board=board, write=True)
|
||||
is False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -240,7 +264,9 @@ async def test_require_board_access_raises_when_no_member(monkeypatch: pytest.Mo
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_member_access_update_deletes_existing_and_adds_rows_when_not_all_boards() -> None:
|
||||
async def test_apply_member_access_update_deletes_existing_and_adds_rows_when_not_all_boards() -> (
|
||||
None
|
||||
):
|
||||
member = OrganizationMember(id=uuid4(), organization_id=uuid4(), user_id=uuid4(), role="member")
|
||||
update = OrganizationMemberAccessUpdate(
|
||||
all_boards_read=False,
|
||||
@@ -263,7 +289,9 @@ async def test_apply_member_access_update_deletes_existing_and_adds_rows_when_no
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_invite_to_member_upgrades_role_and_merges_access_rows(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def test_apply_invite_to_member_upgrades_role_and_merges_access_rows(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
org_id = uuid4()
|
||||
member = OrganizationMember(
|
||||
id=uuid4(),
|
||||
@@ -294,10 +322,12 @@ async def test_apply_invite_to_member_upgrades_role_and_merges_access_rows(monke
|
||||
|
||||
# 1st exec: invite access rows list
|
||||
# 2nd exec: existing access (none)
|
||||
session = _FakeSession(exec_results=[
|
||||
session = _FakeSession(
|
||||
exec_results=[
|
||||
[invite_access],
|
||||
_FakeExecResult(first_value=None),
|
||||
])
|
||||
]
|
||||
)
|
||||
|
||||
await organizations.apply_invite_to_member(session, member=member, invite=invite)
|
||||
|
||||
|
||||
@@ -130,7 +130,9 @@ async def test_dependency_queries_and_replace_and_dependents() -> None:
|
||||
# cover empty input short-circuit
|
||||
assert await td.dependency_status_by_id(session, board_id=board_id, dependency_ids=[]) == {}
|
||||
|
||||
status_map = await td.dependency_status_by_id(session, board_id=board_id, dependency_ids=[t2, t3])
|
||||
status_map = await td.dependency_status_by_id(
|
||||
session, board_id=board_id, dependency_ids=[t2, t3]
|
||||
)
|
||||
assert status_map[t2] == td.DONE_STATUS
|
||||
assert status_map[t3] != td.DONE_STATUS
|
||||
|
||||
|
||||
@@ -408,9 +408,7 @@ export default function EditAgentPage() {
|
||||
type="checkbox"
|
||||
className="mt-1 h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-200"
|
||||
checked={resolvedIsGatewayMain}
|
||||
onChange={(event) =>
|
||||
setIsGatewayMain(event.target.checked)
|
||||
}
|
||||
onChange={(event) => setIsGatewayMain(event.target.checked)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<span>
|
||||
@@ -418,8 +416,8 @@ export default function EditAgentPage() {
|
||||
Gateway main agent
|
||||
</span>
|
||||
<span className="block text-xs text-slate-500">
|
||||
Uses the gateway main session key and is not tied to a
|
||||
single board.
|
||||
Uses the gateway main session key and is not tied to a single
|
||||
board.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
@@ -471,9 +469,7 @@ export default function EditAgentPage() {
|
||||
</label>
|
||||
<Input
|
||||
value={resolvedHeartbeatEvery}
|
||||
onChange={(event) =>
|
||||
setHeartbeatEvery(event.target.value)
|
||||
}
|
||||
onChange={(event) => setHeartbeatEvery(event.target.value)}
|
||||
placeholder="e.g. 10m"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
@@ -244,10 +244,7 @@ export default function NewAgentPage() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{EMOJI_OPTIONS.map((option) => (
|
||||
<SelectItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
>
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.glyph} {option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -284,9 +281,7 @@ export default function NewAgentPage() {
|
||||
</label>
|
||||
<Textarea
|
||||
value={soulTemplate}
|
||||
onChange={(event) =>
|
||||
setSoulTemplate(event.target.value)
|
||||
}
|
||||
onChange={(event) => setSoulTemplate(event.target.value)}
|
||||
rows={10}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
@@ -305,9 +300,7 @@ export default function NewAgentPage() {
|
||||
</label>
|
||||
<Input
|
||||
value={heartbeatEvery}
|
||||
onChange={(event) =>
|
||||
setHeartbeatEvery(event.target.value)
|
||||
}
|
||||
onChange={(event) => setHeartbeatEvery(event.target.value)}
|
||||
placeholder="e.g. 10m"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
|
||||
@@ -21,7 +21,10 @@ import { StatusPill } from "@/components/atoms/StatusPill";
|
||||
import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { ConfirmActionDialog } from "@/components/ui/confirm-action-dialog";
|
||||
import { TableEmptyStateRow, TableLoadingRow } from "@/components/ui/table-state";
|
||||
import {
|
||||
TableEmptyStateRow,
|
||||
TableLoadingRow,
|
||||
} from "@/components/ui/table-state";
|
||||
|
||||
import { ApiError } from "@/api/mutator";
|
||||
import {
|
||||
@@ -257,7 +260,9 @@ export default function AgentsPage() {
|
||||
description={`${agents.length} agent${agents.length === 1 ? "" : "s"} total.`}
|
||||
headerActions={
|
||||
agents.length > 0 ? (
|
||||
<Button onClick={() => router.push("/agents/new")}>New agent</Button>
|
||||
<Button onClick={() => router.push("/agents/new")}>
|
||||
New agent
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
isAdmin={isAdmin}
|
||||
@@ -330,7 +335,9 @@ export default function AgentsPage() {
|
||||
</div>
|
||||
|
||||
{agentsQuery.error ? (
|
||||
<p className="mt-4 text-sm text-red-500">{agentsQuery.error.message}</p>
|
||||
<p className="mt-4 text-sm text-red-500">
|
||||
{agentsQuery.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
</DashboardPageLayout>
|
||||
|
||||
@@ -345,8 +352,7 @@ export default function AgentsPage() {
|
||||
title="Delete agent"
|
||||
description={
|
||||
<>
|
||||
This will remove {deleteTarget?.name}. This action cannot be
|
||||
undone.
|
||||
This will remove {deleteTarget?.name}. This action cannot be undone.
|
||||
</>
|
||||
}
|
||||
errorMessage={deleteMutation.error?.message}
|
||||
|
||||
@@ -293,8 +293,7 @@ export default function EditBoardGroupPage() {
|
||||
{assignFailedCount && Number.isFinite(assignFailedCount) ? (
|
||||
<div className="rounded-xl border border-amber-200 bg-amber-50 p-4 text-sm text-amber-900 shadow-sm">
|
||||
Group was created, but {assignFailedCount} board assignment
|
||||
{assignFailedCount === 1 ? "" : "s"} failed. You can retry
|
||||
below.
|
||||
{assignFailedCount === 1 ? "" : "s"} failed. You can retry below.
|
||||
</div>
|
||||
) : null}
|
||||
<div className="grid gap-6 md:grid-cols-2">
|
||||
@@ -329,8 +328,8 @@ export default function EditBoardGroupPage() {
|
||||
<div>
|
||||
<p className="text-sm font-medium text-slate-900">Boards</p>
|
||||
<p className="mt-1 text-xs text-slate-500">
|
||||
Assign boards to this group to share context across
|
||||
related work.
|
||||
Assign boards to this group to share context across related
|
||||
work.
|
||||
</p>
|
||||
</div>
|
||||
<span className="text-xs text-slate-500">
|
||||
@@ -371,8 +370,7 @@ export default function EditBoardGroupPage() {
|
||||
})
|
||||
.map((board) => {
|
||||
const checked = selectedBoardIds.has(board.id);
|
||||
const isInThisGroup =
|
||||
board.board_group_id === groupId;
|
||||
const isInThisGroup = board.board_group_id === groupId;
|
||||
const isAlreadyGrouped =
|
||||
Boolean(board.board_group_id) && !isInThisGroup;
|
||||
return (
|
||||
|
||||
@@ -164,9 +164,7 @@ export default function NewBoardGroupPage() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<label className="text-sm font-medium text-slate-900">
|
||||
Boards
|
||||
</label>
|
||||
<label className="text-sm font-medium text-slate-900">Boards</label>
|
||||
<span className="text-xs text-slate-500">
|
||||
{selectedBoardIds.size} selected
|
||||
</span>
|
||||
@@ -203,9 +201,7 @@ export default function NewBoardGroupPage() {
|
||||
})
|
||||
.map((board) => {
|
||||
const checked = selectedBoardIds.has(board.id);
|
||||
const isAlreadyGrouped = Boolean(
|
||||
board.board_group_id,
|
||||
);
|
||||
const isAlreadyGrouped = Boolean(board.board_group_id);
|
||||
return (
|
||||
<li key={board.id} className="px-4 py-3">
|
||||
<label className="flex cursor-pointer items-start gap-3">
|
||||
@@ -250,8 +246,8 @@ export default function NewBoardGroupPage() {
|
||||
</div>
|
||||
<p className="text-xs text-slate-500">
|
||||
Optional. Selected boards will be assigned to this group after
|
||||
creation. You can change membership later in group edit or
|
||||
board settings.
|
||||
creation. You can change membership later in group edit or board
|
||||
settings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@ import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout"
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { ConfirmActionDialog } from "@/components/ui/confirm-action-dialog";
|
||||
import { formatTimestamp } from "@/lib/formatters";
|
||||
import { TableEmptyStateRow, TableLoadingRow } from "@/components/ui/table-state";
|
||||
import {
|
||||
TableEmptyStateRow,
|
||||
TableLoadingRow,
|
||||
} from "@/components/ui/table-state";
|
||||
|
||||
export default function BoardGroupsPage() {
|
||||
const { isSignedIn } = useAuth();
|
||||
@@ -258,7 +261,9 @@ export default function BoardGroupsPage() {
|
||||
</div>
|
||||
|
||||
{groupsQuery.error ? (
|
||||
<p className="mt-4 text-sm text-red-500">{groupsQuery.error.message}</p>
|
||||
<p className="mt-4 text-sm text-red-500">
|
||||
{groupsQuery.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
</DashboardPageLayout>
|
||||
<ConfirmActionDialog
|
||||
|
||||
@@ -316,8 +316,7 @@ export default function EditBoardPage() {
|
||||
Goal needs confirmation
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-amber-800/80">
|
||||
Start onboarding to draft an objective and success
|
||||
metrics.
|
||||
Start onboarding to draft an objective and success metrics.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
@@ -366,10 +365,7 @@ export default function EditBoardPage() {
|
||||
<label className="text-sm font-medium text-slate-900">
|
||||
Board type
|
||||
</label>
|
||||
<Select
|
||||
value={resolvedBoardType}
|
||||
onValueChange={setBoardType}
|
||||
>
|
||||
<Select value={resolvedBoardType} onValueChange={setBoardType}>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select board type" />
|
||||
</SelectTrigger>
|
||||
@@ -397,8 +393,8 @@ export default function EditBoardPage() {
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<p className="text-xs text-slate-500">
|
||||
Boards in the same group can share cross-board context
|
||||
for agents.
|
||||
Boards in the same group can share cross-board context for
|
||||
agents.
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
@@ -408,9 +404,7 @@ export default function EditBoardPage() {
|
||||
<Input
|
||||
type="date"
|
||||
value={resolvedTargetDate}
|
||||
onChange={(event) =>
|
||||
setTargetDate(event.target.value)
|
||||
}
|
||||
onChange={(event) => setTargetDate(event.target.value)}
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</div>
|
||||
@@ -435,9 +429,7 @@ export default function EditBoardPage() {
|
||||
</label>
|
||||
<Textarea
|
||||
value={resolvedSuccessMetrics}
|
||||
onChange={(event) =>
|
||||
setSuccessMetrics(event.target.value)
|
||||
}
|
||||
onChange={(event) => setSuccessMetrics(event.target.value)}
|
||||
placeholder='e.g. { "target": "Launch by week 2" }'
|
||||
className="min-h-[140px] font-mono text-xs"
|
||||
disabled={isLoading}
|
||||
@@ -453,8 +445,7 @@ export default function EditBoardPage() {
|
||||
{gateways.length === 0 ? (
|
||||
<div className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
|
||||
<p>
|
||||
No gateways available. Create one in Gateways to
|
||||
continue.
|
||||
No gateways available. Create one in Gateways to continue.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
@@ -225,7 +225,9 @@ export default function NewBoardPage() {
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{errorMessage ? <p className="text-sm text-red-500">{errorMessage}</p> : null}
|
||||
{errorMessage ? (
|
||||
<p className="text-sm text-red-500">{errorMessage}</p>
|
||||
) : null}
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<Button
|
||||
|
||||
@@ -31,7 +31,10 @@ import type { BoardGroupRead, BoardRead } from "@/api/generated/model";
|
||||
import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { ConfirmActionDialog } from "@/components/ui/confirm-action-dialog";
|
||||
import { TableEmptyStateRow, TableLoadingRow } from "@/components/ui/table-state";
|
||||
import {
|
||||
TableEmptyStateRow,
|
||||
TableLoadingRow,
|
||||
} from "@/components/ui/table-state";
|
||||
|
||||
const compactId = (value: string) =>
|
||||
value.length > 8 ? `${value.slice(0, 8)}…` : value;
|
||||
@@ -312,7 +315,9 @@ export default function BoardsPage() {
|
||||
</div>
|
||||
|
||||
{boardsQuery.error ? (
|
||||
<p className="mt-4 text-sm text-red-500">{boardsQuery.error.message}</p>
|
||||
<p className="mt-4 text-sm text-red-500">
|
||||
{boardsQuery.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
</DashboardPageLayout>
|
||||
<ConfirmActionDialog
|
||||
@@ -326,8 +331,7 @@ export default function BoardsPage() {
|
||||
title="Delete board"
|
||||
description={
|
||||
<>
|
||||
This will remove {deleteTarget?.name}. This action cannot be
|
||||
undone.
|
||||
This will remove {deleteTarget?.name}. This action cannot be undone.
|
||||
</>
|
||||
}
|
||||
errorMessage={deleteMutation.error?.message}
|
||||
|
||||
@@ -166,9 +166,7 @@ export default function GatewayDetailPage() {
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase text-slate-400">
|
||||
Token
|
||||
</p>
|
||||
<p className="text-xs uppercase text-slate-400">Token</p>
|
||||
<p className="mt-1 text-sm font-medium text-slate-900">
|
||||
{maskToken(gateway.token)}
|
||||
</p>
|
||||
@@ -199,17 +197,13 @@ export default function GatewayDetailPage() {
|
||||
</div>
|
||||
<div className="grid gap-3 sm:grid-cols-2">
|
||||
<div>
|
||||
<p className="text-xs uppercase text-slate-400">
|
||||
Created
|
||||
</p>
|
||||
<p className="text-xs uppercase text-slate-400">Created</p>
|
||||
<p className="mt-1 text-sm font-medium text-slate-900">
|
||||
{formatTimestamp(gateway.created_at)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-xs uppercase text-slate-400">
|
||||
Updated
|
||||
</p>
|
||||
<p className="text-xs uppercase text-slate-400">Updated</p>
|
||||
<p className="mt-1 text-sm font-medium text-slate-900">
|
||||
{formatTimestamp(gateway.updated_at)}
|
||||
</p>
|
||||
@@ -259,9 +253,7 @@ export default function GatewayDetailPage() {
|
||||
<p className="text-sm font-medium text-slate-900">
|
||||
{agent.name}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">
|
||||
{agent.id}
|
||||
</p>
|
||||
<p className="text-xs text-slate-500">{agent.id}</p>
|
||||
</td>
|
||||
<td className="px-4 py-3 text-sm text-slate-700">
|
||||
{agent.status}
|
||||
|
||||
@@ -19,7 +19,10 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { DashboardPageLayout } from "@/components/templates/DashboardPageLayout";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
import { ConfirmActionDialog } from "@/components/ui/confirm-action-dialog";
|
||||
import { TableEmptyStateRow, TableLoadingRow } from "@/components/ui/table-state";
|
||||
import {
|
||||
TableEmptyStateRow,
|
||||
TableLoadingRow,
|
||||
} from "@/components/ui/table-state";
|
||||
|
||||
import { ApiError } from "@/api/mutator";
|
||||
import {
|
||||
@@ -267,7 +270,14 @@ export default function GatewaysPage() {
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<rect x="2" y="7" width="20" height="14" rx="2" ry="2" />
|
||||
<rect
|
||||
x="2"
|
||||
y="7"
|
||||
width="20"
|
||||
height="14"
|
||||
rx="2"
|
||||
ry="2"
|
||||
/>
|
||||
<path d="M16 21V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16" />
|
||||
</svg>
|
||||
}
|
||||
@@ -283,7 +293,9 @@ export default function GatewaysPage() {
|
||||
</div>
|
||||
|
||||
{gatewaysQuery.error ? (
|
||||
<p className="mt-4 text-sm text-red-500">{gatewaysQuery.error.message}</p>
|
||||
<p className="mt-4 text-sm text-red-500">
|
||||
{gatewaysQuery.error.message}
|
||||
</p>
|
||||
) : null}
|
||||
</DashboardPageLayout>
|
||||
|
||||
|
||||
@@ -378,7 +378,9 @@ export default function OrganizationPage() {
|
||||
});
|
||||
|
||||
const membershipRole =
|
||||
membershipQuery.data?.status === 200 ? membershipQuery.data.data.role : null;
|
||||
membershipQuery.data?.status === 200
|
||||
? membershipQuery.data.data.role
|
||||
: null;
|
||||
const isOwner = membershipRole === "owner";
|
||||
const isAdmin = membershipRole === "admin" || membershipRole === "owner";
|
||||
|
||||
@@ -842,7 +844,9 @@ export default function OrganizationPage() {
|
||||
onClick={() => setInviteDialogOpen(true)}
|
||||
disabled={!isAdmin}
|
||||
title={
|
||||
isAdmin ? undefined : "Only organization admins can invite"
|
||||
isAdmin
|
||||
? undefined
|
||||
: "Only organization admins can invite"
|
||||
}
|
||||
>
|
||||
<UserPlus className="h-4 w-4" />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
|
||||
import { ActivityFeed } from "./ActivityFeed";
|
||||
|
||||
type Item = { id: string; label: string };
|
||||
@@ -56,9 +55,7 @@ describe("ActivityFeed", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByText("Waiting for new comments…"),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText("Waiting for new comments…")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("When agents post updates, they will show up here."),
|
||||
).toBeInTheDocument();
|
||||
|
||||
@@ -159,10 +159,17 @@ export function GatewayForm({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{errorMessage ? <p className="text-sm text-red-500">{errorMessage}</p> : null}
|
||||
{errorMessage ? (
|
||||
<p className="text-sm text-red-500">{errorMessage}</p>
|
||||
) : null}
|
||||
|
||||
<div className="flex justify-end gap-3">
|
||||
<Button type="button" variant="ghost" onClick={onCancel} disabled={isLoading}>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onCancel}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
<Button type="submit" disabled={isLoading || !canSubmit}>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createExponentialBackoff } from "./backoff";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
|
||||
describe("createExponentialBackoff", () => {
|
||||
it("uses default options", () => {
|
||||
const backoff = createExponentialBackoff();
|
||||
|
||||
@@ -50,7 +50,8 @@ export async function checkGatewayConnection(params: {
|
||||
} catch (error) {
|
||||
return {
|
||||
ok: false,
|
||||
message: error instanceof Error ? error.message : "Unable to reach gateway.",
|
||||
message:
|
||||
error instanceof Error ? error.message : "Unable to reach gateway.",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,10 @@ export default defineConfig({
|
||||
reportsDirectory: "./coverage",
|
||||
// Policy (scoped gate): require 100% coverage on *explicitly listed* unit-testable modules first.
|
||||
// We'll expand this include list as we add more unit/component tests.
|
||||
include: ["src/lib/backoff.ts", "src/components/activity/ActivityFeed.tsx"],
|
||||
include: [
|
||||
"src/lib/backoff.ts",
|
||||
"src/components/activity/ActivityFeed.tsx",
|
||||
],
|
||||
exclude: ["**/*.d.ts", "src/**/__generated__/**", "src/**/generated/**"],
|
||||
thresholds: {
|
||||
lines: 100,
|
||||
|
||||
Reference in New Issue
Block a user