diff --git a/.flow/tasks/fn-16.13.json b/.flow/tasks/fn-16.13.json new file mode 100644 index 000000000..89be26f85 --- /dev/null +++ b/.flow/tasks/fn-16.13.json @@ -0,0 +1,21 @@ +{ + "assignee": "bordumbb@gmail.com", + "claim_note": "", + "claimed_at": "2026-09-26T02:11:50.122844Z", + "created_at": "2026-09-26T02:08:42.244248Z", + "depends_on": [], + "epic": "fn-16", + "evidence": { + "commits": [], + "prs": [], + "tests": [ + "DATABASE_URL=... uv run --no-sync pytest python-packages/dataing/tests/integration/api/test_investigation_snapshot_import.py -m integration --no-cov" + ] + }, + "id": "fn-16.13", + "priority": null, + "spec_path": ".flow/tasks/fn-16.13.md", + "status": "done", + "title": "Snapshot import: stop inserting generated investigations.status", + "updated_at": "2026-09-26T02:18:24.729448Z" +} diff --git a/.flow/tasks/fn-16.13.md b/.flow/tasks/fn-16.13.md new file mode 100644 index 000000000..a3f752726 --- /dev/null +++ b/.flow/tasks/fn-16.13.md @@ -0,0 +1,19 @@ +# fn-16.13 Snapshot import: stop inserting generated investigations.status + +## Description +TBD + +## Acceptance +## Acceptance +- POST /investigations/import no longer writes the generated `status` column +- Integration test (migrated schema) imports a valid archive: 200, row stored with replay outcome, status derived as 'completed' + + +## Done summary +Dropped the generated `status` column (and its "imported" value) from the snapshot import INSERT; status now derives from `outcome` ('completed'). +Test: tests/integration/api/test_investigation_snapshot_import.py builds a real archive with SnapshotBuilder and imports it against the migrated schema (RED: GeneratedAlwaysError, GREEN after fix). +Also added shared `fixtures/database.py` (migrated_dsn/migrated_db) re-exported by CE and EE integration conftests. +## Evidence +- Commits: +- Tests: DATABASE_URL=... uv run --no-sync pytest python-packages/dataing/tests/integration/api/test_investigation_snapshot_import.py -m integration --no-cov +- PRs: diff --git a/.flow/tasks/fn-16.14.json b/.flow/tasks/fn-16.14.json new file mode 100644 index 000000000..28526e91e --- /dev/null +++ b/.flow/tasks/fn-16.14.json @@ -0,0 +1,21 @@ +{ + "assignee": "bordumbb@gmail.com", + "claim_note": "", + "claimed_at": "2026-09-26T02:18:24.996044Z", + "created_at": "2026-09-26T02:08:42.507450Z", + "depends_on": [], + "epic": "fn-16", + "evidence": { + "commits": [], + "prs": [], + "tests": [ + "DATABASE_URL=... uv run --no-sync pytest python-packages/dataing-ee/tests/integration/core/automation/test_spawn_investigation.py -m integration --no-cov" + ] + }, + "id": "fn-16.14", + "priority": null, + "spec_path": ".flow/tasks/fn-16.14.md", + "status": "done", + "title": "Automation spawn_investigation: valid AnomalyAlert via InvestigationStarterService", + "updated_at": "2026-09-26T02:29:51.445475Z" +} diff --git a/.flow/tasks/fn-16.14.md b/.flow/tasks/fn-16.14.md new file mode 100644 index 000000000..c1ed72dd2 --- /dev/null +++ b/.flow/tasks/fn-16.14.md @@ -0,0 +1,22 @@ +# fn-16.14 Automation spawn_investigation: valid AnomalyAlert via InvestigationStarterService + +## Description +TBD + +## Acceptance +## Acceptance +- `ActionExecutor._spawn_investigation` stores `alert` as a valid AnomalyAlert dump + `datasource_id`, via InvestigationStarterService (row + Temporal workflow) +- Issue linked through `issue_investigation_runs` (trigger_type 'rule'); re-running returns the existing investigation +- Fails cleanly (ActionResult.success False) when the issue has no dataset or no datasource resolves +- Integration tests against the migrated schema + + +## Done summary +`_spawn_investigation` now builds an AnomalyAlert from the issue row (description-type metric, dataset from the issue, date from created_at, source_* from the issue) and starts it through InvestigationStarterService (alert JSONB = AnomalyAlert dump + datasource_id, Temporal workflow started). +ExecutionContext gains a required `investigation_starter`. Datasource: `datasource_id` action param (must belong to the tenant), else the tenant's only active datasource, else a failed ActionResult. +Issue link + dedupe via issue_investigation_runs (trigger_type 'rule', trigger_ref {rule_id}, execution_profile = profile). +Tests: dataing-ee/tests/integration/core/automation/test_spawn_investigation.py (6 cases; RED: `column "issue_id" does not exist`). +## Evidence +- Commits: +- Tests: DATABASE_URL=... uv run --no-sync pytest python-packages/dataing-ee/tests/integration/core/automation/test_spawn_investigation.py -m integration --no-cov +- PRs: diff --git a/.flow/tasks/fn-16.15.json b/.flow/tasks/fn-16.15.json new file mode 100644 index 000000000..4476b49e4 --- /dev/null +++ b/.flow/tasks/fn-16.15.json @@ -0,0 +1,22 @@ +{ + "assignee": "bordumbb@gmail.com", + "claim_note": "", + "claimed_at": "2026-09-26T02:29:52.197558Z", + "created_at": "2026-09-26T02:08:42.791298Z", + "depends_on": [], + "epic": "fn-16", + "evidence": { + "commits": [], + "prs": [], + "tests": [ + "uv run --no-sync mypy python-packages/dataing/src python-packages/dataing-ee/src", + "uv run --no-sync pytest python-packages/dataing/tests --no-cov" + ] + }, + "id": "fn-16.15", + "priority": null, + "spec_path": ".flow/tasks/fn-16.15.md", + "status": "done", + "title": "Delete dead AppDatabase.create_investigation", + "updated_at": "2026-09-26T02:58:33.360753Z" +} diff --git a/.flow/tasks/fn-16.15.md b/.flow/tasks/fn-16.15.md new file mode 100644 index 000000000..2065ab0c2 --- /dev/null +++ b/.flow/tasks/fn-16.15.md @@ -0,0 +1,17 @@ +# fn-16.15 Delete dead AppDatabase.create_investigation + +## Description +TBD + +## Acceptance +## Acceptance +- `AppDatabase.create_investigation` (old-schema insert, no callers) deleted + + +## Done summary +Deleted `AppDatabase.create_investigation` (pre-013 column insert). No callers: the other `create_investigation` hits are PostgresInvestigationRepository / the repository protocol / an entitlements test route. +Verified: grep, mypy clean, CE 2044 passed / EE 458 passed. +## Evidence +- Commits: +- Tests: uv run --no-sync mypy python-packages/dataing/src python-packages/dataing-ee/src, uv run --no-sync pytest python-packages/dataing/tests --no-cov +- PRs: diff --git a/.flow/tasks/fn-16.16.json b/.flow/tasks/fn-16.16.json new file mode 100644 index 000000000..9cf05a375 --- /dev/null +++ b/.flow/tasks/fn-16.16.json @@ -0,0 +1,23 @@ +{ + "assignee": "bordumbb@gmail.com", + "claim_note": "", + "claimed_at": "2026-09-26T02:33:18.975571Z", + "created_at": "2026-09-26T02:08:43.082252Z", + "depends_on": [], + "epic": "fn-16", + "evidence": { + "commits": [], + "prs": [], + "tests": [ + "uv run --no-sync pytest python-packages/dataing/tests --no-cov", + "uv run --no-sync pytest python-packages/dataing-ee/tests --no-cov", + "pnpm typecheck && pnpm lint && pnpm test" + ] + }, + "id": "fn-16.16", + "priority": null, + "spec_path": ".flow/tasks/fn-16.16.md", + "status": "done", + "title": "Delete approvals stack (pre-013 approval_requests)", + "updated_at": "2026-09-26T02:58:33.643752Z" +} diff --git a/.flow/tasks/fn-16.16.md b/.flow/tasks/fn-16.16.md new file mode 100644 index 000000000..023a0fa9a --- /dev/null +++ b/.flow/tasks/fn-16.16.md @@ -0,0 +1,21 @@ +# fn-16.16 Delete approvals stack (pre-013 approval_requests) + +## Description +TBD + +## Acceptance +## Acceptance +- /approvals route, AppDatabase approval methods and `update_investigation_status` deleted (pre-013 approval_requests schema; no producer; Temporal HITL uses POST /investigations/{id}/input) +- Frontend ContextReviewPage, its route and the approval notification link removed; client regenerated +- ruff, ruff format --check, mypy, CE/EE unit tests and frontend checks pass + + +## Done summary +Deleted the approvals stack (user decision): routes/approvals.py + registration; AppDatabase create_approval_request / get_pending_approvals / make_approval_decision / update_investigation_status; dashboard pendingApprovals KPI (API field + card); dead producers notify_approval_required, EmailNotifier.send_approval_required, Slack approval.required formatting (+ their unit tests); frontend ContextReviewPage + context-review, /approvals route, notification-card approval branch, notification/webhook settings entries, query-keys. +OpenAPI: removed the 7 approvals paths + 7 approval-only schemas + DashboardStats.pending_approvals from the committed spec (full regen adds +3222/-2087 unrelated drift); orval regenerated; 23 stale generated approval files + 22 index exports removed by hand (orval does not clean). +Left + flagged: EE require_approval_for_queries setting (unenforced), domain_types Approval* models (unused), dashboard other old-schema queries. +Verified: CE 2044 passed, EE 458 passed, mypy clean, ruff clean, frontend tsc/eslint/prettier/vitest (12) clean. +## Evidence +- Commits: +- Tests: uv run --no-sync pytest python-packages/dataing/tests --no-cov, uv run --no-sync pytest python-packages/dataing-ee/tests --no-cov, pnpm typecheck && pnpm lint && pnpm test +- PRs: diff --git a/frontend/app/src/App.tsx b/frontend/app/src/App.tsx index 9185d3769..aa55546b7 100644 --- a/frontend/app/src/App.tsx +++ b/frontend/app/src/App.tsx @@ -27,7 +27,6 @@ import { DashboardPage } from "@/features/dashboard/dashboard-page"; import { InvestigationList } from "@/features/investigation/InvestigationList"; import { InvestigationDetail } from "@/features/investigation/InvestigationDetail"; import { NewInvestigation } from "@/features/investigation/NewInvestigation"; -import { ContextReviewPage } from "@/features/investigation/ContextReviewPage"; import { DataSourcePage } from "@/features/datasources/datasource-page"; import { DatasetListPage, DatasetDetailPage } from "@/features/datasets"; import { SettingsPage } from "@/features/settings/settings-page"; @@ -167,14 +166,6 @@ function AppWithEntitlements() { } /> - - - - } - /> {/* Stats Grid */} -
+
@@ -93,32 +86,6 @@ export function DashboardPage() { )} - - - - - Pending Approvals - - - - - {isLoading ? ( - - ) : ( - <> -
- {stats?.pendingApprovals ?? 0} -
- {(stats?.pendingApprovals ?? 0) > 0 && ( -

- - Requires attention -

- )} - - )} -
-
{/* Recent Investigations */} diff --git a/frontend/app/src/features/investigation/ContextReviewPage.tsx b/frontend/app/src/features/investigation/ContextReviewPage.tsx deleted file mode 100644 index 9c2f6fd2a..000000000 --- a/frontend/app/src/features/investigation/ContextReviewPage.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { useParams, useNavigate, Link } from "react-router-dom"; -import { ArrowLeft, Loader2 } from "lucide-react"; -import { toast } from "sonner"; -import { useQueryClient } from "@tanstack/react-query"; - -import { Button } from "@/components/ui/Button"; -import { Card, CardContent } from "@/components/ui/Card"; -import { EmptyState } from "@/components/shared/empty-state"; -import { - useGetApprovalRequestApiV1ApprovalsApprovalIdGet, - useApproveRequestApiV1ApprovalsApprovalIdApprovePost, - useRejectRequestApiV1ApprovalsApprovalIdRejectPost, - getListPendingApprovalsApiV1ApprovalsPendingGetQueryKey, -} from "@/lib/api/generated/approvals/approvals"; -import { ContextReview } from "./context-review"; - -export function ContextReviewPage() { - const { approvalId } = useParams<{ approvalId: string }>(); - const navigate = useNavigate(); - const queryClient = useQueryClient(); - - const { - data: approval, - isLoading, - error, - } = useGetApprovalRequestApiV1ApprovalsApprovalIdGet(approvalId || "", { - query: { enabled: !!approvalId }, - }); - - const approveMutation = useApproveRequestApiV1ApprovalsApprovalIdApprovePost({ - mutation: { - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: getListPendingApprovalsApiV1ApprovalsPendingGetQueryKey(), - }); - toast.success("Investigation approved and resumed"); - navigate(`/investigations/${approval?.investigation_id}`); - }, - onError: (err: Error) => { - toast.error(`Failed to approve: ${err.message}`); - }, - }, - }); - - const rejectMutation = useRejectRequestApiV1ApprovalsApprovalIdRejectPost({ - mutation: { - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: getListPendingApprovalsApiV1ApprovalsPendingGetQueryKey(), - }); - toast.success("Investigation rejected"); - navigate("/investigations"); - }, - onError: (err: Error) => { - toast.error(`Failed to reject: ${err.message}`); - }, - }, - }); - - const handleApprove = async (comment?: string) => { - if (!approvalId) return; - await approveMutation.mutateAsync({ - approvalId, - data: { comment: comment || null }, - }); - }; - - const handleReject = async (reason: string) => { - if (!approvalId) return; - await rejectMutation.mutateAsync({ - approvalId, - data: { reason }, - }); - }; - - if (!approvalId) { - return ( - - - - - - ); - } - - if (isLoading) { - return ( -
- -
- ); - } - - if (error || !approval) { - return ( - - - - - - } - /> - - - ); - } - - // Check if already decided - if (approval.decision) { - return ( -
-
- - - -

Context Review

-
- - - - - - } - /> - - -
- ); - } - - // Build context object for ContextReview component - const context = { - query: (approval.context?.query as string) || "Query not available", - purpose: - (approval.context?.purpose as string) || - `Review context for investigation ${approval.investigation_id}`, - tables_accessed: (approval.context?.tables_accessed as string[]) || [], - estimated_rows: (approval.context?.estimated_rows as number) || 0, - }; - - return ( -
-
- - - -

Context Review

-
- - -
- ); -} diff --git a/frontend/app/src/features/investigation/context-review.tsx b/frontend/app/src/features/investigation/context-review.tsx deleted file mode 100644 index e2b09d3f5..000000000 --- a/frontend/app/src/features/investigation/context-review.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import * as React from "react"; -import { Check, X, AlertCircle } from "lucide-react"; -import { toast } from "sonner"; - -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/Card"; -import { Button } from "@/components/ui/Button"; -import { Textarea } from "@/components/ui/textarea"; -import { Badge } from "@/components/ui/Badge"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; - -interface ContextReviewProps { - investigationId: string; - context: { - query: string; - purpose: string; - tables_accessed: string[]; - estimated_rows: number; - }; - onApprove: (comment?: string) => void; - onReject: (reason: string) => void; -} - -export function ContextReview({ - investigationId, - context, - onApprove, - onReject, -}: ContextReviewProps) { - const [comment, setComment] = React.useState(""); - const [rejectReason, setRejectReason] = React.useState(""); - const [showRejectForm, setShowRejectForm] = React.useState(false); - const [isSubmitting, setIsSubmitting] = React.useState(false); - - const handleApprove = async () => { - setIsSubmitting(true); - try { - await onApprove(comment || undefined); - toast.success("Context approved"); - } catch (error) { - toast.error("Failed to approve context"); - } finally { - setIsSubmitting(false); - } - }; - - const handleReject = async () => { - if (!rejectReason.trim()) { - toast.error("Please provide a reason for rejection"); - return; - } - setIsSubmitting(true); - try { - await onReject(rejectReason); - toast.success("Context rejected"); - } catch (error) { - toast.error("Failed to reject context"); - } finally { - setIsSubmitting(false); - } - }; - - return ( - - -
-
- Human-in-the-Loop Review - - Review and approve the proposed context for investigation{" "} - {investigationId} - -
- Pending Approval -
-
- - - - Context Review Required - - The system is requesting approval to access the following data. - Please review carefully before approving. - - - -
-
-

Purpose

-

{context.purpose}

-
- -
-

Tables to Access

-
- {context.tables_accessed.map((table) => ( - - {table} - - ))} -
-
- -
-

Estimated Data Volume

-

- Approximately {context.estimated_rows.toLocaleString()} rows will - be analyzed -

-
- -
-

Query

-
-              {context.query}
-            
-
-
- - {!showRejectForm ? ( - <> -
-

Comment (Optional)

-