From 53fd3316af080986998313c27dd41f5085af1bc6 Mon Sep 17 00:00:00 2001 From: florianbgt Date: Mon, 7 Jul 2025 11:15:16 +0200 Subject: [PATCH 1/2] ai filter feature --- .env.example | 5 +- deno.json | 5 +- packages/app/deno.json | 3 +- .../DependencyVisualizer.tsx | 16 +- .../components/FileExplorerSidebar.tsx | 232 ++++++-- .../contextMenu/FileContextMenu.tsx | 4 +- .../contextMenu/SymbolContextMenu.tsx | 7 +- .../ControlExtensions/MetricsExtension.tsx | 71 ++- .../detailsPanes/FileDetailsPane.tsx | 10 +- .../components/detailsPanes/Metrics.tsx | 35 +- .../detailsPanes/SymbolDetailsPane.tsx | 16 +- .../cytoscape/elements/file.ts | 53 +- .../cytoscape/elements/project.ts | 10 +- .../cytoscape/elements/symbol.ts | 49 +- .../cytoscape/elements/types.ts | 24 +- .../fileDependencyVisualizer/index.ts | 41 +- .../cytoscape/label/index.ts | 10 +- .../cytoscape/metrics/index.ts | 36 +- .../projectDependencyVisualizer/index.ts | 17 +- .../cytoscape/styles/index.ts | 30 +- .../symbolDependencyVisualizer/index.ts | 42 +- .../visualizers/FileVisualizer.tsx | 34 +- .../visualizers/ProjectVisualizer.tsx | 25 +- .../visualizers/SymbolVisualizer.tsx | 23 +- packages/app/src/contexts/CoreApi.tsx | 78 ++- packages/app/src/contexts/Workspace.tsx | 8 +- packages/app/src/guards/APIReady.tsx | 20 + packages/app/src/pages/index.tsx | 8 +- packages/app/src/pages/invitations/claim.tsx | 5 +- packages/app/src/pages/login.tsx | 8 +- packages/app/src/pages/profile.tsx | 12 +- packages/app/src/pages/projects/add.tsx | 6 +- packages/app/src/pages/projects/index.tsx | 6 +- .../app/src/pages/projects/project/base.tsx | 10 +- .../projects/project/manifests/index.tsx | 36 +- .../projects/project/manifests/manifest.tsx | 28 +- .../src/pages/projects/project/settings.tsx | 4 +- packages/app/src/pages/workspaces/add.tsx | 6 +- .../src/pages/workspaces/workspace/index.tsx | 6 +- .../pages/workspaces/workspace/members.tsx | 30 +- .../workspaces/workspace/subscription.tsx | 139 +++-- packages/app/vite.config.ts | 10 +- packages/core/deno.json | 7 +- packages/core/src/api/auth/middleware.ts | 11 +- packages/core/src/api/auth/router.test.ts | 57 +- packages/core/src/api/auth/router.ts | 11 +- packages/core/src/api/auth/service.ts | 16 +- packages/core/src/api/auth/types.ts | 46 -- packages/core/src/api/billing/router.test.ts | 152 +++-- packages/core/src/api/billing/router.ts | 66 ++- packages/core/src/api/billing/service.ts | 143 ++--- .../core/src/api/invitation/router.test.ts | 34 +- packages/core/src/api/invitation/router.ts | 7 +- packages/core/src/api/invitation/service.ts | 6 +- packages/core/src/api/invitation/types.ts | 24 - packages/core/src/api/manifest/router.test.ts | 73 +-- packages/core/src/api/manifest/router.ts | 83 ++- packages/core/src/api/manifest/service.ts | 87 ++- packages/core/src/api/manifest/types.ts | 89 --- packages/core/src/api/member/router.test.ts | 43 +- packages/core/src/api/member/router.ts | 8 +- packages/core/src/api/member/service.ts | 11 +- packages/core/src/api/project/router.test.ts | 40 +- packages/core/src/api/project/router.ts | 53 +- packages/core/src/api/project/service.ts | 11 +- packages/core/src/api/project/types.ts | 118 ---- packages/core/src/api/responseType.ts | 19 - packages/core/src/api/token/router.test.ts | 36 +- packages/core/src/api/token/router.ts | 5 +- packages/core/src/api/token/service.ts | 6 +- .../core/src/api/workspace/router.test.ts | 22 +- packages/core/src/api/workspace/router.ts | 36 +- packages/core/src/api/workspace/service.ts | 24 +- packages/core/src/db/database.ts | 22 +- .../core/src/db/migrations/0002.migration.ts | 39 ++ packages/core/src/db/models/member.ts | 7 +- packages/core/src/db/models/workspace.ts | 16 - packages/core/src/db/vectorStore.ts | 106 ++++ packages/core/src/email/index.ts | 21 +- .../src/email/templates/DowngradeEmail.tsx | 13 +- .../core/src/email/templates/UpgradeEmail.tsx | 13 +- packages/core/src/index.ts | 2 + .../core/src/manifest/auditManifest/types.ts | 42 -- packages/core/src/manifest/service.ts | 282 ++++----- .../core/src/manifest/smartFilter/graph.ts | 196 +++++++ .../core/src/manifest/smartFilter/index.ts | 22 + .../core/src/manifest/smartFilter/tools.ts | 552 ++++++++++++++++++ packages/core/src/manifest/types.ts | 23 - packages/core/src/settings.ts | 29 +- packages/core/src/stripe/index.ts | 53 +- packages/shared/deno.json | 7 + packages/shared/src/api/auth.ts | 48 ++ .../types.ts => shared/src/api/billing.ts} | 80 ++- packages/shared/src/api/invitation.ts | 31 + packages/shared/src/api/manifest.ts | 146 +++++ .../types.ts => shared/src/api/member.ts} | 36 +- packages/shared/src/api/project.ts | 163 ++++++ .../types.ts => shared/src/api/token.ts} | 30 +- .../types.ts => shared/src/api/workspace.ts} | 51 +- packages/shared/src/index.ts | 14 + .../shared/src/manifests/auditManifest.ts | 23 + .../src/manifests/dependencyManifest.ts} | 83 +-- packages/shared/src/member.ts | 4 + packages/shared/src/stripe.ts | 17 + 104 files changed, 3106 insertions(+), 1627 deletions(-) create mode 100644 packages/app/src/guards/APIReady.tsx delete mode 100644 packages/core/src/api/auth/types.ts delete mode 100644 packages/core/src/api/invitation/types.ts delete mode 100644 packages/core/src/api/manifest/types.ts delete mode 100644 packages/core/src/api/project/types.ts delete mode 100644 packages/core/src/api/responseType.ts create mode 100644 packages/core/src/db/migrations/0002.migration.ts create mode 100644 packages/core/src/db/vectorStore.ts delete mode 100644 packages/core/src/manifest/auditManifest/types.ts create mode 100644 packages/core/src/manifest/smartFilter/graph.ts create mode 100644 packages/core/src/manifest/smartFilter/index.ts create mode 100644 packages/core/src/manifest/smartFilter/tools.ts delete mode 100644 packages/core/src/manifest/types.ts create mode 100644 packages/shared/deno.json create mode 100644 packages/shared/src/api/auth.ts rename packages/{core/src/api/billing/types.ts => shared/src/api/billing.ts} (55%) create mode 100644 packages/shared/src/api/invitation.ts create mode 100644 packages/shared/src/api/manifest.ts rename packages/{core/src/api/member/types.ts => shared/src/api/member.ts} (67%) create mode 100644 packages/shared/src/api/project.ts rename packages/{core/src/api/token/types.ts => shared/src/api/token.ts} (70%) rename packages/{core/src/api/workspace/types.ts => shared/src/api/workspace.ts} (65%) create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/src/manifests/auditManifest.ts rename packages/{core/src/manifest/dependencyManifest/types.ts => shared/src/manifests/dependencyManifest.ts} (51%) create mode 100644 packages/shared/src/member.ts create mode 100644 packages/shared/src/stripe.ts diff --git a/.env.example b/.env.example index 12e7a7a..8ab6a09 100644 --- a/.env.example +++ b/.env.example @@ -17,4 +17,7 @@ STRIPE_PRODUCT_PREMIUM_YEARLY_PRICE_ID=price_premium_yearly_price_id_here # Development/Testing Flags, for convenience SKIP_OTP=true EMAIL_USE_CONSOLE=true -GCP_USE_FAKE_GCS_SERVER=true \ No newline at end of file +GCP_USE_FAKE_GCS_SERVER=true + +# AI API keys +GOOGLE_AI_API_KEY=your_api_key \ No newline at end of file diff --git a/deno.json b/deno.json index 3db69e5..7b07291 100644 --- a/deno.json +++ b/deno.json @@ -1,7 +1,8 @@ { "workspace": [ "./packages/app", - "./packages/core" + "./packages/core", + "./packages/shared" ], "nodeModulesDir": "auto", "lock": false, @@ -12,7 +13,7 @@ "build:app": "deno task --config ./packages/app/deno.json build", "build:core": "deno task --config ./packages/core/deno.json build", "test": "STRIPE_USE_MOCK=true GCP_USE_FAKE_GCS_SERVER=true deno test -A", - "db:docker": "docker container rm stackcore-pg --force || true && docker run --name stackcore-pg -e POSTGRES_PASSWORD=password -e POSTGRES_USER=user -e POSTGRES_DB=core -p 5432:5432 -d postgres:17", + "db:docker": "docker container rm stackcore-pg --force || true && docker run --name stackcore-pg -e POSTGRES_PASSWORD=password -e POSTGRES_USER=user -e POSTGRES_DB=core -p 5432:5432 -d pgvector/pgvector:pg17", "db:migrate": "deno task --config ./packages/core/deno.json migrate -A", "stripe:create-products": "deno run -A --env-file=.env ./packages/core/src/stripe/scripts/createProducts.ts", "stripe:cli": "docker run --rm -it --env-file=.env stripe/stripe-cli:latest", diff --git a/packages/app/deno.json b/packages/app/deno.json index 9c1fc20..da46310 100644 --- a/packages/app/deno.json +++ b/packages/app/deno.json @@ -15,9 +15,8 @@ "@radix-ui/react-slot": "npm:@radix-ui/react-slot@^1.2.3", "@radix-ui/react-tabs": "npm:@radix-ui/react-tabs@^1.1.12", "@radix-ui/react-tooltip": "npm:@radix-ui/react-tooltip@^1.2.7", - "@stackcore/core/responses": "../core/src/api/responseType.ts", - "@stackcore/core/manifest": "../core/src/manifest/types.ts", "@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.4", + "@std/path": "jsr:@std/path@^1.1.1", "@tailwindcss/vite": "npm:@tailwindcss/vite@^4.1.8", "@tanstack/react-table": "npm:@tanstack/react-table@^8.21.3", "@types/cytoscape-fcose": "npm:@types/cytoscape-fcose@^2.2.4", diff --git a/packages/app/src/components/DependencyVisualizer/DependencyVisualizer.tsx b/packages/app/src/components/DependencyVisualizer/DependencyVisualizer.tsx index 1122036..dea1337 100644 --- a/packages/app/src/components/DependencyVisualizer/DependencyVisualizer.tsx +++ b/packages/app/src/components/DependencyVisualizer/DependencyVisualizer.tsx @@ -1,9 +1,9 @@ import { useState } from "react"; import { useSearchParams } from "react-router"; import type { - AuditManifest, - DependencyManifest, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import { SidebarProvider, SidebarTrigger } from "../shadcn/Sidebar.tsx"; import { FileExplorerSidebar } from "./components/FileExplorerSidebar.tsx"; import BreadcrumbNav from "./components/BreadcrumNav.tsx"; @@ -13,8 +13,8 @@ import SymbolVisualizer from "./visualizers/SymbolVisualizer.tsx"; export interface VisualizerContext { manifestId: number; - dependencyManifest: DependencyManifest; - auditManifest: AuditManifest; + dependencyManifest: dependencyManifestTypes.DependencyManifest; + auditManifest: auditManifestTypes.AuditManifest; highlightedCytoscapeRef: { filePath: string; symbolId: string | undefined; @@ -23,11 +23,10 @@ export interface VisualizerContext { export default function DependencyVisualizer(props: { manifestId: number; - dependencyManifest: DependencyManifest; - auditManifest: AuditManifest; + dependencyManifest: dependencyManifestTypes.DependencyManifest; + auditManifest: auditManifestTypes.AuditManifest; }) { const [searchParams] = useSearchParams(); - const [highlightedCytoscapeRef, setHighlightedCytoscapeRef] = useState< { filePath: string; @@ -45,6 +44,7 @@ export default function DependencyVisualizer(props: { className="grow flex min-h-0" > { diff --git a/packages/app/src/components/DependencyVisualizer/components/FileExplorerSidebar.tsx b/packages/app/src/components/DependencyVisualizer/components/FileExplorerSidebar.tsx index 45e2ced..69a87de 100644 --- a/packages/app/src/components/DependencyVisualizer/components/FileExplorerSidebar.tsx +++ b/packages/app/src/components/DependencyVisualizer/components/FileExplorerSidebar.tsx @@ -1,9 +1,10 @@ import { useEffect, useState } from "react"; import { Link } from "react-router"; -import type { - AuditManifest, - DependencyManifest, -} from "@stackcore/core/manifest"; +import { + type auditManifestTypes, + type dependencyManifestTypes, + manifestApiTypes, +} from "@stackcore/shared"; import { Sidebar, SidebarContent, @@ -27,9 +28,17 @@ import { FolderOpen, ScanEye, SearchCode, + Sparkles, + X, } from "lucide-react"; import { ScrollArea, ScrollBar } from "../../shadcn/Scrollarea.tsx"; import DisplayNameWithTooltip from "./DisplayNameWithTootip.tsx"; +import { toast } from "sonner"; +import { useCoreApi } from "../../../contexts/CoreApi.tsx"; +import z from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { Form, FormField } from "../../shadcn/Form.tsx"; export interface ExplorerNodeData { id: string; @@ -40,24 +49,94 @@ export interface ExplorerNodeData { } export function FileExplorerSidebar(props: { - dependencyManifest: DependencyManifest; - auditManifest: AuditManifest; + dependencyManifestId: number; + dependencyManifest: dependencyManifestTypes.DependencyManifest; + auditManifest: auditManifestTypes.AuditManifest; onHighlightInCytoscape: (node: ExplorerNodeData) => void; toDetails: (node: ExplorerNodeData) => string; }) { - const [search, setSearch] = useState(""); + const coreApi = useCoreApi(); + + const [busy, setBusy] = useState(false); + const [filteredSymbols, setFilteredSymbols] = useState< + { fileId: string; symbolId: string }[] + >([]); const [explorerTree, setExplorerTree] = useState(); // Build the explorer tree when the dependency manifest changes useEffect(() => { - const tree = buildExplorerTree(props.dependencyManifest, search); + const tree = buildExplorerTree(props.dependencyManifest, []); setExplorerTree(tree); - }, [props.dependencyManifest, search]); + }, [props.dependencyManifest]); + + const searchSchema = z.object({ + search: z.string().min(1, "Search is required"), + }); + + const form = useForm>({ + resolver: zodResolver(searchSchema), + defaultValues: { + search: "", + }, + }); + + async function onSubmitSearch(values: z.infer) { + setBusy(true); + try { + const { + url: smartFilterUrl, + method: smartFilterMethod, + body: smartFilterBody, + } = manifestApiTypes.prepareSmartFilter( + props.dependencyManifestId, + { + prompt: values.search, + }, + ); + + const response = await coreApi.handleRequest( + smartFilterUrl, + smartFilterMethod, + smartFilterBody, + ); + + if (!response.ok) { + throw new Error("Failed to search for files"); + } + + const responseData = await response + .json() as manifestApiTypes.SmartFilterResponse; + + if (!responseData.success) { + toast.error(responseData.message); + return; + } + + const filteredSymbols = responseData.results; + + setFilteredSymbols(filteredSymbols); + + const tree = buildExplorerTree(props.dependencyManifest, filteredSymbols); + setExplorerTree(tree); + + toast.success(responseData.message); + } catch (error) { + console.error(error); + toast.error("Error searching for files"); + } finally { + setBusy(false); + } + } + + function onClearSearch() { + setFilteredSymbols([]); + setExplorerTree(buildExplorerTree(props.dependencyManifest, [])); + } function buildExplorerTree( - dependencyManifest: DependencyManifest, - search: string, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + filteredSymbols: { fileId: string; symbolId: string }[], ): ExplorerNodeData | undefined { const getExplorerNodeId = (filePath: string, instanceId?: string) => { if (instanceId) { @@ -72,28 +151,30 @@ export function FileExplorerSidebar(props: { children: new Map(), }; - // Filter function to check if a string matches the search term - const matchesSearch = (text: string): boolean => { - if (!search) return true; - return text.toLowerCase().includes(search.toLowerCase()); - }; + // Create a set for faster lookup of filtered symbols + const filteredSymbolsSet = new Set( + filteredSymbols.map((s) => `${s.fileId}#${s.symbolId}`), + ); + + // Create a set of files that have filtered symbols + const filesWithFilteredSymbols = new Set( + filteredSymbols.map((s) => s.fileId), + ); - // Track if any nodes match the search to avoid empty results + // If no filtered symbols provided, show everything + const shouldShowAll = filteredSymbols.length === 0; + + // Track if any nodes match the filter to avoid empty results let hasMatchingNodes = false; for (const fileDependencyManifest of Object.values(dependencyManifest)) { const filePath = fileDependencyManifest.filePath; - const fileName = filePath.split("/").pop() || ""; - const fileMatchesSearch = matchesSearch(fileName); - // Check if any symbols match the search - const matchingSymbols = Object.keys(fileDependencyManifest.symbols) - .filter( - (symbolId) => matchesSearch(symbolId), - ); + // Check if this file should be included + const fileShouldBeIncluded = shouldShowAll || + filesWithFilteredSymbols.has(filePath); - // Skip this file if neither the file nor any symbols match the search - if (search && !fileMatchesSearch && matchingSymbols.length === 0) { + if (!fileShouldBeIncluded) { continue; } @@ -114,9 +195,10 @@ export function FileExplorerSidebar(props: { } currentNode.fileId = getExplorerNodeId(filePath); - // Only add symbols that match the search or if no search is provided + // Add symbols - either all symbols if no filter, or only filtered symbols for (const instanceId of Object.keys(fileDependencyManifest.symbols)) { - if (!search || matchesSearch(instanceId)) { + const symbolKey = `${filePath}#${instanceId}`; + if (shouldShowAll || filteredSymbolsSet.has(symbolKey)) { const id = getExplorerNodeId(filePath, instanceId); currentNode.children.set(id, { id: id, @@ -129,8 +211,8 @@ export function FileExplorerSidebar(props: { } } - // If no nodes match the search, return an empty tree - if (search && !hasMatchingNodes) { + // If no nodes match the filter, return an empty tree + if (!shouldShowAll && !hasMatchingNodes) { return undefined; } @@ -138,10 +220,28 @@ export function FileExplorerSidebar(props: { // First recursively flatten all children if (node.children.size > 0) { const flattenedChildren = new Map(); + + // Sort children into folders and files + const folders: Array<[string, ExplorerNodeData]> = []; + const files: Array<[string, ExplorerNodeData]> = []; + for (const [id, child] of node.children) { const flattenedChild = flattenTree(child); - flattenedChildren.set(id, flattenedChild); + if (flattenedChild.fileId) { + files.push([id, flattenedChild]); + } else { + folders.push([id, flattenedChild]); + } + } + + // Add folders first, then files + for (const [id, folder] of folders) { + flattenedChildren.set(id, folder); + } + for (const [id, file] of files) { + flattenedChildren.set(id, file); } + node.children = flattenedChildren; } @@ -167,7 +267,9 @@ export function FileExplorerSidebar(props: { }; // Flatten nodes that have only one child - return flattenTree(root); + const flattenedTree = flattenTree(root); + + return flattenedTree; } return ( @@ -186,23 +288,57 @@ export function FileExplorerSidebar(props: { - - - setSearch(e.target.value)} - placeholder="Search" +
+ + ( + + + + + +
+ AI-powered search for code exploration. +
+ Filter by type (e.g. "show all classes"), +
+ by metrics (e.g. "functions with high complexity"), +
+ or by business domain (e.g. "auth related code"). +
+
+
+ )} /> - - -
- Search for a file or symbol. -
- The search will find partial matches in both symbol names and - file paths. -
-
- + + {filteredSymbols.length > 0 && ( + + )} + +
{!explorerTree diff --git a/packages/app/src/components/DependencyVisualizer/components/contextMenu/FileContextMenu.tsx b/packages/app/src/components/DependencyVisualizer/components/contextMenu/FileContextMenu.tsx index c6f9cbb..3b449b9 100644 --- a/packages/app/src/components/DependencyVisualizer/components/contextMenu/FileContextMenu.tsx +++ b/packages/app/src/components/DependencyVisualizer/components/contextMenu/FileContextMenu.tsx @@ -1,5 +1,5 @@ import { Link, useSearchParams } from "react-router"; -import type { DependencyManifest } from "@stackcore/core/manifest"; +import type { dependencyManifestTypes } from "@stackcore/shared"; import { DropdownMenu, DropdownMenuContent, @@ -14,7 +14,7 @@ import DisplayNameWithTooltip from "../DisplayNameWithTootip.tsx"; export default function FileContextMenu(props: { context: { position: { x: number; y: number }; - fileDependencyManifest: DependencyManifest[string]; + fileDependencyManifest: dependencyManifestTypes.DependencyManifest[string]; } | undefined; onClose: () => void; onOpenDetails: (filePath: string) => void; diff --git a/packages/app/src/components/DependencyVisualizer/components/contextMenu/SymbolContextMenu.tsx b/packages/app/src/components/DependencyVisualizer/components/contextMenu/SymbolContextMenu.tsx index f90f822..2ad42d5 100644 --- a/packages/app/src/components/DependencyVisualizer/components/contextMenu/SymbolContextMenu.tsx +++ b/packages/app/src/components/DependencyVisualizer/components/contextMenu/SymbolContextMenu.tsx @@ -1,5 +1,5 @@ import { Link, useSearchParams } from "react-router"; -import type { DependencyManifest } from "@stackcore/core/manifest"; +import type { dependencyManifestTypes } from "@stackcore/shared"; import { DropdownMenu, DropdownMenuContent, @@ -14,8 +14,9 @@ import DisplayNameWithTooltip from "../DisplayNameWithTootip.tsx"; export default function SymbolContextMenu(props: { context: { position: { x: number; y: number }; - fileDependencyManifest: DependencyManifest[string]; - symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; + fileDependencyManifest: dependencyManifestTypes.DependencyManifest[string]; + symbolDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]["symbols"][string]; } | undefined; onClose: () => void; onOpenDetails: (filePath: string, symbolId: string) => void; diff --git a/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/MetricsExtension.tsx b/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/MetricsExtension.tsx index bdbbae3..0491402 100644 --- a/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/MetricsExtension.tsx +++ b/packages/app/src/components/DependencyVisualizer/components/controls/ControlExtensions/MetricsExtension.tsx @@ -1,13 +1,4 @@ -import { - type Metric, - metricCharacterCount, - metricCodeCharacterCount, - metricCodeLineCount, - metricCyclomaticComplexity, - metricDependencyCount, - metricDependentCount, - metricLinesCount, -} from "@stackcore/core/manifest"; +import { dependencyManifestTypes } from "@stackcore/shared"; import { Tooltip, TooltipContent, @@ -25,32 +16,32 @@ import { export default function MetricsExtension(props: { busy: boolean; metricState: { - metric: Metric | undefined; - setMetric: (metric: Metric | undefined) => void; + metric: dependencyManifestTypes.Metric | undefined; + setMetric: (metric: dependencyManifestTypes.Metric | undefined) => void; }; }) { const metric = props.metricState.metric; - function getMetricLabel(metric: Metric | undefined) { - if (metric === metricLinesCount) { + function getMetricLabel(metric: dependencyManifestTypes.Metric | undefined) { + if (metric === dependencyManifestTypes.metricLinesCount) { return "Lines"; } - if (metric === metricCodeLineCount) { + if (metric === dependencyManifestTypes.metricCodeLineCount) { return "Code Lines"; } - if (metric === metricCharacterCount) { + if (metric === dependencyManifestTypes.metricCharacterCount) { return "Chars"; } - if (metric === metricCodeCharacterCount) { + if (metric === dependencyManifestTypes.metricCodeCharacterCount) { return "Code Chars"; } - if (metric === metricDependencyCount) { + if (metric === dependencyManifestTypes.metricDependencyCount) { return "Dependencies"; } - if (metric === metricDependentCount) { + if (metric === dependencyManifestTypes.metricDependentCount) { return "Dependents"; } - if (metric === metricCyclomaticComplexity) { + if (metric === dependencyManifestTypes.metricCyclomaticComplexity) { return "Complexity"; } else { return "None"; @@ -73,14 +64,38 @@ export default function MetricsExtension(props: { {([ { metric: undefined, label: "No Metric" }, - { metric: metricLinesCount, label: "Lines" }, - { metric: metricCodeLineCount, label: "Code Lines" }, - { metric: metricCharacterCount, label: "Total Characters" }, - { metric: metricCodeCharacterCount, label: "Code Characters" }, - { metric: metricDependencyCount, label: "Dependencies" }, - { metric: metricDependentCount, label: "Dependents" }, - { metric: metricCyclomaticComplexity, label: "Complexity" }, - ] as { metric: Metric | undefined; label: string }[]).map((val) => ( + { + metric: dependencyManifestTypes.metricLinesCount, + label: "Lines", + }, + { + metric: dependencyManifestTypes.metricCodeLineCount, + label: "Code Lines", + }, + { + metric: dependencyManifestTypes.metricCharacterCount, + label: "Total Characters", + }, + { + metric: dependencyManifestTypes.metricCodeCharacterCount, + label: "Code Characters", + }, + { + metric: dependencyManifestTypes.metricDependencyCount, + label: "Dependencies", + }, + { + metric: dependencyManifestTypes.metricDependentCount, + label: "Dependents", + }, + { + metric: dependencyManifestTypes.metricCyclomaticComplexity, + label: "Complexity", + }, + ] as { + metric: dependencyManifestTypes.Metric | undefined; + label: string; + }[]).map((val) => ( props.metricState?.setMetric?.(val.metric)} diff --git a/packages/app/src/components/DependencyVisualizer/components/detailsPanes/FileDetailsPane.tsx b/packages/app/src/components/DependencyVisualizer/components/detailsPanes/FileDetailsPane.tsx index b0f3e3c..2bf2472 100644 --- a/packages/app/src/components/DependencyVisualizer/components/detailsPanes/FileDetailsPane.tsx +++ b/packages/app/src/components/DependencyVisualizer/components/detailsPanes/FileDetailsPane.tsx @@ -1,7 +1,7 @@ import type { - AuditManifest, - DependencyManifest, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import { Sheet, SheetContent, @@ -26,8 +26,8 @@ import SymbolExtractionDialog from "../SymbolExtractionDialog.tsx"; export default function FileDetailsPane(props: { context: { manifestId: number; - fileDependencyManifest: DependencyManifest[string]; - fileAuditManifest: AuditManifest[string]; + fileDependencyManifest: dependencyManifestTypes.DependencyManifest[string]; + fileAuditManifest: auditManifestTypes.AuditManifest[string]; } | undefined; onClose: () => void; }) { diff --git a/packages/app/src/components/DependencyVisualizer/components/detailsPanes/Metrics.tsx b/packages/app/src/components/DependencyVisualizer/components/detailsPanes/Metrics.tsx index c13d10c..51e34ae 100644 --- a/packages/app/src/components/DependencyVisualizer/components/detailsPanes/Metrics.tsx +++ b/packages/app/src/components/DependencyVisualizer/components/detailsPanes/Metrics.tsx @@ -1,41 +1,34 @@ import { - type AuditManifest, - type DependencyManifest, - metricCharacterCount, - metricCodeCharacterCount, - metricCodeLineCount, - metricCyclomaticComplexity, - metricDependencyCount, - metricDependentCount, - metricLinesCount, -} from "@stackcore/core/manifest"; + type auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import { Alert, AlertDescription } from "../../../shadcn/Alert.tsx"; export default function Metrics(props: { dependencyManifest: - | DependencyManifest[string] - | DependencyManifest[string]["symbols"][string] + | dependencyManifestTypes.DependencyManifest[string] + | dependencyManifestTypes.DependencyManifest[string]["symbols"][string] | undefined; auditManifest: - | AuditManifest[string] - | AuditManifest[string]["symbols"][string] + | auditManifestTypes.AuditManifest[string] + | auditManifestTypes.AuditManifest[string]["symbols"][string] | undefined; }) { function metricToHumanString(metric: string) { switch (metric) { - case metricLinesCount: + case dependencyManifestTypes.metricLinesCount: return "Lines"; - case metricCodeLineCount: + case dependencyManifestTypes.metricCodeLineCount: return "Code Lines"; - case metricCharacterCount: + case dependencyManifestTypes.metricCharacterCount: return "Characters"; - case metricCodeCharacterCount: + case dependencyManifestTypes.metricCodeCharacterCount: return "Code Characters"; - case metricDependencyCount: + case dependencyManifestTypes.metricDependencyCount: return "Dependencies"; - case metricDependentCount: + case dependencyManifestTypes.metricDependentCount: return "Dependents"; - case metricCyclomaticComplexity: + case dependencyManifestTypes.metricCyclomaticComplexity: return "Cyclomatic Complexity"; default: return metric; diff --git a/packages/app/src/components/DependencyVisualizer/components/detailsPanes/SymbolDetailsPane.tsx b/packages/app/src/components/DependencyVisualizer/components/detailsPanes/SymbolDetailsPane.tsx index 5ee1b1a..d873841 100644 --- a/packages/app/src/components/DependencyVisualizer/components/detailsPanes/SymbolDetailsPane.tsx +++ b/packages/app/src/components/DependencyVisualizer/components/detailsPanes/SymbolDetailsPane.tsx @@ -1,7 +1,7 @@ import type { - AuditManifest, - DependencyManifest, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import { Sheet, SheetContent, @@ -27,10 +27,12 @@ import SymbolExtractionDialog from "../SymbolExtractionDialog.tsx"; export default function SymbolDetailsPane(props: { context: { manifestId: number; - fileDependencyManifest: DependencyManifest[string]; - symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; - fileAuditManifest: AuditManifest[string]; - symbolAuditManifest: AuditManifest[string]["symbols"][string]; + fileDependencyManifest: dependencyManifestTypes.DependencyManifest[string]; + symbolDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]["symbols"][string]; + fileAuditManifest: auditManifestTypes.AuditManifest[string]; + symbolAuditManifest: + auditManifestTypes.AuditManifest[string]["symbols"][string]; } | undefined; onClose: () => void; }) { diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/elements/file.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/elements/file.ts index 901bea6..c663639 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/elements/file.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/elements/file.ts @@ -1,15 +1,7 @@ import type { - AuditManifest, - DependencyManifest, - metricCharacterCount, - metricCodeCharacterCount, - metricCodeLineCount, - metricCyclomaticComplexity, - metricDependencyCount, - metricDependentCount, - metricLinesCount, - SymbolType, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import { getCollapsedSymbolNodeLabel, getExpandedSymbolNodeLabel, @@ -30,13 +22,13 @@ function createNodeData(params: { symbolType: string; isExternal: boolean; metricsSeverity: { - [metricLinesCount]: number; - [metricCodeLineCount]: number; - [metricCodeCharacterCount]: number; - [metricCharacterCount]: number; - [metricDependencyCount]: number; - [metricDependentCount]: number; - [metricCyclomaticComplexity]: number; + [dependencyManifestTypes.metricLinesCount]: number; + [dependencyManifestTypes.metricCodeLineCount]: number; + [dependencyManifestTypes.metricCodeCharacterCount]: number; + [dependencyManifestTypes.metricCharacterCount]: number; + [dependencyManifestTypes.metricDependencyCount]: number; + [dependencyManifestTypes.metricDependentCount]: number; + [dependencyManifestTypes.metricCyclomaticComplexity]: number; }; expandedLabel: string; collapsedLabel: string; @@ -79,16 +71,18 @@ interface CustomNodeDefinition extends NodeDefinition { } function processDependencies( - symbol: DependencyManifest[string]["symbols"][string], + symbol: dependencyManifestTypes.DependencyManifest[string]["symbols"][string], symbolNodeId: string, - dependencyManifest: DependencyManifest, - auditManifest: AuditManifest, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + auditManifest: auditManifestTypes.AuditManifest, nodes: CustomNodeDefinition[], edges: EdgeDefinition[], ) { Object.values(symbol.dependencies).forEach((dep) => { - let depDependencyManifest: DependencyManifest[string] | undefined; - let depAuditManifest: AuditManifest[string] | undefined; + let depDependencyManifest: + | dependencyManifestTypes.DependencyManifest[string] + | undefined; + let depAuditManifest: auditManifestTypes.AuditManifest[string] | undefined; if (!dep.isExternal) { depDependencyManifest = dependencyManifest[dep.id]; @@ -105,7 +99,8 @@ function processDependencies( ); if (!existingNode) { - let depSymbolType: SymbolType | "unknown" = "unknown"; + let depSymbolType: dependencyManifestTypes.SymbolType | "unknown" = + "unknown"; if (depDependencyManifest) { depSymbolType = depDependencyManifest.symbols[depSymbolName].type; } @@ -156,10 +151,10 @@ function processDependencies( } function processDependents( - symbol: DependencyManifest[string]["symbols"][string], + symbol: dependencyManifestTypes.DependencyManifest[string]["symbols"][string], symbolNodeId: string, - dependencyManifest: DependencyManifest, - auditManifest: AuditManifest, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + auditManifest: auditManifestTypes.AuditManifest, nodes: CustomNodeDefinition[], edges: EdgeDefinition[], ) { @@ -222,8 +217,8 @@ function processDependents( export function getSymbolElementsInFile( fileId: string, - dependencyManifest: DependencyManifest, - auditManifest: AuditManifest, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + auditManifest: auditManifestTypes.AuditManifest, ) { const fileManifest = dependencyManifest[fileId]; if (!fileManifest) { diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/elements/project.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/elements/project.ts index 6cd7497..7dec873 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/elements/project.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/elements/project.ts @@ -4,9 +4,9 @@ import type { NodeDefinition, } from "cytoscape"; import type { - AuditManifest, - DependencyManifest, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import { getCollapsedFileNodeLabel, getExpandedFileNodeLabel, @@ -16,8 +16,8 @@ import { getMetricsSeverityForNode } from "../metrics/index.ts"; import type { FileNapiNodeData } from "./types.ts"; export function getFileElementsInProject( - dependencyManifest: DependencyManifest, - auditManifest: AuditManifest, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + auditManifest: auditManifestTypes.AuditManifest, ): ElementDefinition[] { interface CustomNodeDefinition extends NodeDefinition { data: FileNapiNodeData & object; diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/elements/symbol.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/elements/symbol.ts index 6f27f0b..91a2a52 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/elements/symbol.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/elements/symbol.ts @@ -1,15 +1,7 @@ import type { - AuditManifest, - DependencyManifest, - metricCharacterCount, - metricCodeCharacterCount, - metricCodeLineCount, - metricCyclomaticComplexity, - metricDependencyCount, - metricDependentCount, - metricLinesCount, - SymbolType, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import { getCollapsedSymbolNodeLabel, getExpandedSymbolNodeLabel, @@ -27,13 +19,13 @@ function createNodeData(params: { symbolType: string; isExternal: boolean; metricsSeverity: { - [metricLinesCount]: number; - [metricCodeLineCount]: number; - [metricCodeCharacterCount]: number; - [metricCharacterCount]: number; - [metricDependencyCount]: number; - [metricDependentCount]: number; - [metricCyclomaticComplexity]: number; + [dependencyManifestTypes.metricLinesCount]: number; + [dependencyManifestTypes.metricCodeLineCount]: number; + [dependencyManifestTypes.metricCodeCharacterCount]: number; + [dependencyManifestTypes.metricCharacterCount]: number; + [dependencyManifestTypes.metricDependencyCount]: number; + [dependencyManifestTypes.metricDependentCount]: number; + [dependencyManifestTypes.metricCyclomaticComplexity]: number; }; expandedLabel: string; collapsedLabel: string; @@ -76,10 +68,10 @@ interface CustomNodeDefinition extends NodeDefinition { } function traverseSymbolGraph( - symbol: DependencyManifest[string]["symbols"][string], + symbol: dependencyManifestTypes.DependencyManifest[string]["symbols"][string], symbolNodeId: string, - dependencyManifest: DependencyManifest, - auditManifest: AuditManifest, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + auditManifest: auditManifestTypes.AuditManifest, nodeMap: Map, edgeMap: Map, currentDepth: number, @@ -90,8 +82,12 @@ function traverseSymbolGraph( // Process dependencies if we haven't reached max depth if (currentDepth < maxDepsDepth) { Object.values(symbol.dependencies).forEach((dep) => { - let depDependencyManifest: DependencyManifest[string] | undefined; - let depAuditManifest: AuditManifest[string] | undefined; + let depDependencyManifest: + | dependencyManifestTypes.DependencyManifest[string] + | undefined; + let depAuditManifest: + | auditManifestTypes.AuditManifest[string] + | undefined; if (!dep.isExternal) { depDependencyManifest = dependencyManifest[dep.id]; @@ -116,7 +112,8 @@ function traverseSymbolGraph( } visited.add(depSymbolNodeId); - let depSymbolType: SymbolType | "unknown" = "unknown"; + let depSymbolType: dependencyManifestTypes.SymbolType | "unknown" = + "unknown"; if (depDependencyManifest) { depSymbolType = depDependencyManifest.symbols[depSymbolName].type; } @@ -268,8 +265,8 @@ export function getSymbolElementsForSymbol( symbolId: string, dependencyDepth: number, dependentDepth: number, - dependencyManifest: DependencyManifest, - auditManifest: AuditManifest, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + auditManifest: auditManifestTypes.AuditManifest, ) { const fileManifest = dependencyManifest[fileName]; if (!fileManifest) { diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/elements/types.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/elements/types.ts index e51ccda..097cf7e 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/elements/types.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/elements/types.ts @@ -1,24 +1,16 @@ -import type { - metricCharacterCount, - metricCodeCharacterCount, - metricCodeLineCount, - metricCyclomaticComplexity, - metricDependencyCount, - metricDependentCount, - metricLinesCount, -} from "@stackcore/core/manifest"; +import type { dependencyManifestTypes } from "@stackcore/shared"; export interface NapiNodeData { id: string; position: { x: number; y: number }; metricsSeverity: { - [metricLinesCount]: number; - [metricCodeLineCount]: number; - [metricCodeCharacterCount]: number; - [metricCharacterCount]: number; - [metricDependencyCount]: number; - [metricDependentCount]: number; - [metricCyclomaticComplexity]: number; + [dependencyManifestTypes.metricLinesCount]: number; + [dependencyManifestTypes.metricCodeLineCount]: number; + [dependencyManifestTypes.metricCodeCharacterCount]: number; + [dependencyManifestTypes.metricCharacterCount]: number; + [dependencyManifestTypes.metricDependencyCount]: number; + [dependencyManifestTypes.metricDependentCount]: number; + [dependencyManifestTypes.metricCyclomaticComplexity]: number; }; expanded: { label: string; diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/fileDependencyVisualizer/index.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/fileDependencyVisualizer/index.ts index 88b6748..d832a1c 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/fileDependencyVisualizer/index.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/fileDependencyVisualizer/index.ts @@ -1,16 +1,7 @@ import { - type AuditManifest, - type DependencyManifest, - type Metric, - symbolTypeClass, - symbolTypeDelegate, - symbolTypeEnum, - symbolTypeFunction, - symbolTypeInterface, - symbolTypeRecord, - symbolTypeStruct, - symbolTypeVariable, -} from "@stackcore/core/manifest"; + type auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import type { Collection, Core, @@ -53,7 +44,7 @@ export class FileDependencyVisualizer { private layout = mainLayout; private fileId: string; /** Current metric used for node coloring */ - private targetMetric: Metric | undefined; + private targetMetric: dependencyManifestTypes.Metric | undefined; /** Currently selected node in the graph */ private selectedNodeId: string | undefined; /** Callback functions triggered by graph interactions */ @@ -70,11 +61,11 @@ export class FileDependencyVisualizer { constructor( container: HTMLElement, fileId: string, - dependencyManifest: DependencyManifest, - auditManifest: AuditManifest, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + auditManifest: auditManifestTypes.AuditManifest, options?: { theme?: "light" | "dark"; - defaultMetric?: Metric | undefined; + defaultMetric?: dependencyManifestTypes.Metric | undefined; onAfterNodeClick?: () => void; onAfterNodeRightClick?: (data: { position: { x: number; y: number }; @@ -185,7 +176,7 @@ export class FileDependencyVisualizer { * * @param metric - The new metric to use for node coloring */ - public setTargetMetric(metric: Metric | undefined) { + public setTargetMetric(metric: dependencyManifestTypes.Metric | undefined) { this.targetMetric = metric; const stylesheet = getCytoscapeStylesheet( @@ -218,14 +209,14 @@ export class FileDependencyVisualizer { } const symbolTypeFilters = { - [symbolTypeVariable]: showVariables, - [symbolTypeFunction]: showFunctions, - [symbolTypeClass]: showClasses, - [symbolTypeStruct]: showStructs, - [symbolTypeEnum]: showEnums, - [symbolTypeInterface]: showInterfaces, - [symbolTypeRecord]: showRecords, - [symbolTypeDelegate]: showDelegates, + [dependencyManifestTypes.symbolTypeVariable]: showVariables, + [dependencyManifestTypes.symbolTypeFunction]: showFunctions, + [dependencyManifestTypes.symbolTypeClass]: showClasses, + [dependencyManifestTypes.symbolTypeStruct]: showStructs, + [dependencyManifestTypes.symbolTypeEnum]: showEnums, + [dependencyManifestTypes.symbolTypeInterface]: showInterfaces, + [dependencyManifestTypes.symbolTypeRecord]: showRecords, + [dependencyManifestTypes.symbolTypeDelegate]: showDelegates, }; for (const [symbolType, show] of Object.entries(symbolTypeFilters)) { if (!show && data.symbolType === symbolType) { diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/label/index.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/label/index.ts index 83a8041..ec82bfc 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/label/index.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/label/index.ts @@ -1,4 +1,4 @@ -import type { AuditManifest } from "@stackcore/core/manifest"; +import type { auditManifestTypes } from "@stackcore/shared"; /** * Calculates the optimal width and height for a node based on its label text. @@ -55,7 +55,7 @@ const errorChar = "⚠️"; */ export function getCollapsedFileNodeLabel(data: { fileName: string; - fileAuditManifest: AuditManifest[string]; + fileAuditManifest: auditManifestTypes.AuditManifest[string]; }) { const fileNameMaxLength = 25; const fileName = data.fileName.length > fileNameMaxLength @@ -88,7 +88,7 @@ export function getCollapsedFileNodeLabel(data: { */ export function getExpandedFileNodeLabel(data: { fileName: string; - fileAuditManifest: AuditManifest[string]; + fileAuditManifest: auditManifestTypes.AuditManifest[string]; }) { let label = data.fileName; @@ -139,7 +139,9 @@ export function getExpandedSymbolNodeLabel(data: { fileName: string; symbolName: string; symbolType: string; - symbolAuditManifest: AuditManifest[string]["symbols"][string] | undefined; + symbolAuditManifest: + | auditManifestTypes.AuditManifest[string]["symbols"][string] + | undefined; }) { // Create the basic label with symbol name and type let label = `${data.symbolName} (${data.symbolType})`; diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/metrics/index.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/metrics/index.ts index f63c1ab..8955f96 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/metrics/index.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/metrics/index.ts @@ -1,14 +1,7 @@ import { - type AuditManifest, - type Metric, - metricCharacterCount, - metricCodeCharacterCount, - metricCodeLineCount, - metricCyclomaticComplexity, - metricDependencyCount, - metricDependentCount, - metricLinesCount, -} from "@stackcore/core/manifest"; + type auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; /** * Extracts metric severity levels from an audit manifest for visualization. @@ -22,23 +15,24 @@ import { */ export function getMetricsSeverityForNode( auditManifest: - | AuditManifest[string] - | AuditManifest[string]["symbols"][string] + | auditManifestTypes.AuditManifest[string] + | auditManifestTypes.AuditManifest[string]["symbols"][string] | undefined, ) { - const metricsSeverity: Record = { - [metricLinesCount]: 0, - [metricCodeLineCount]: 0, - [metricCodeCharacterCount]: 0, - [metricCharacterCount]: 0, - [metricDependencyCount]: 0, - [metricDependentCount]: 0, - [metricCyclomaticComplexity]: 0, + const metricsSeverity: Record = { + [dependencyManifestTypes.metricLinesCount]: 0, + [dependencyManifestTypes.metricCodeLineCount]: 0, + [dependencyManifestTypes.metricCodeCharacterCount]: 0, + [dependencyManifestTypes.metricCharacterCount]: 0, + [dependencyManifestTypes.metricDependencyCount]: 0, + [dependencyManifestTypes.metricDependentCount]: 0, + [dependencyManifestTypes.metricCyclomaticComplexity]: 0, }; if (auditManifest) { Object.entries(auditManifest.alerts).forEach(([metric, value]) => { - metricsSeverity[metric as Metric] = value.severity; + metricsSeverity[metric as dependencyManifestTypes.Metric] = + value.severity; }); } diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/projectDependencyVisualizer/index.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/projectDependencyVisualizer/index.ts index 0adf624..71c98cf 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/projectDependencyVisualizer/index.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/projectDependencyVisualizer/index.ts @@ -6,10 +6,9 @@ import cytoscape, { import type { Core } from "cytoscape"; import fcose from "cytoscape-fcose"; import type { - AuditManifest, - DependencyManifest, - Metric, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import type { NapiNodeData } from "../elements/types.ts"; import { mainLayout } from "../layout/index.ts"; import { getCytoscapeStylesheet } from "../styles/index.ts"; @@ -41,7 +40,7 @@ export class ProjectDependencyVisualizer { /** Layout configuration for organizing the dependency graph */ private layout = mainLayout; /** Current metric used for node coloring */ - private targetMetric: Metric | undefined; + private targetMetric: dependencyManifestTypes.Metric | undefined; /** Currently selected node in the graph */ private selectedNodeId: string | undefined; /** Callback functions triggered by graph interactions */ @@ -64,11 +63,11 @@ export class ProjectDependencyVisualizer { */ constructor( container: HTMLElement, - dependencyManifest: DependencyManifest, - auditManifest: AuditManifest, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + auditManifest: auditManifestTypes.AuditManifest, options?: { theme?: "light" | "dark"; - defaultMetric?: Metric | undefined; + defaultMetric?: dependencyManifestTypes.Metric | undefined; onAfterNodeClick?: () => void; onAfterNodeRightClick?: (data: { position: { x: number; y: number }; @@ -260,7 +259,7 @@ export class ProjectDependencyVisualizer { * * @param metric - The new metric to use for node coloring (e.g., LOC, characters, dependencies) */ - public setTargetMetric(metric: Metric | undefined) { + public setTargetMetric(metric: dependencyManifestTypes.Metric | undefined) { this.targetMetric = metric; const stylesheet = getCytoscapeStylesheet( diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/styles/index.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/styles/index.ts index 23a60f6..e7d2c2a 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/styles/index.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/styles/index.ts @@ -1,15 +1,5 @@ import type { NodeSingular, StylesheetJson } from "cytoscape"; -import { - type Metric, - symbolTypeClass, - symbolTypeDelegate, - symbolTypeEnum, - symbolTypeFunction, - symbolTypeInterface, - symbolTypeRecord, - symbolTypeStruct, - symbolTypeVariable, -} from "@stackcore/core/manifest"; +import { dependencyManifestTypes } from "@stackcore/shared"; import type { NapiNodeData, SymbolNapiNodeData } from "../elements/types.ts"; interface CytoscapeStyles { @@ -112,7 +102,7 @@ function getCytoscapeStyles(theme: "light" | "dark" = "light") { } export function getCytoscapeStylesheet( - targetMetric: Metric | undefined, + targetMetric: dependencyManifestTypes.Metric | undefined, theme: "light" | "dark" = "light", ) { const styles = getCytoscapeStyles(theme); @@ -166,16 +156,16 @@ export function getCytoscapeStylesheet( const data = node.data() as SymbolNapiNodeData; if (data.isExternal) return "octagon"; switch (data.symbolType) { - case symbolTypeClass: - case symbolTypeInterface: - case symbolTypeStruct: - case symbolTypeEnum: - case symbolTypeRecord: + case dependencyManifestTypes.symbolTypeClass: + case dependencyManifestTypes.symbolTypeInterface: + case dependencyManifestTypes.symbolTypeStruct: + case dependencyManifestTypes.symbolTypeEnum: + case dependencyManifestTypes.symbolTypeRecord: return "hexagon"; - case symbolTypeFunction: - case symbolTypeDelegate: + case dependencyManifestTypes.symbolTypeFunction: + case dependencyManifestTypes.symbolTypeDelegate: return "roundrectangle"; - case symbolTypeVariable: + case dependencyManifestTypes.symbolTypeVariable: return "ellipse"; default: return "ellipse"; diff --git a/packages/app/src/components/DependencyVisualizer/cytoscape/symbolDependencyVisualizer/index.ts b/packages/app/src/components/DependencyVisualizer/cytoscape/symbolDependencyVisualizer/index.ts index cb0ce65..62a4dac 100644 --- a/packages/app/src/components/DependencyVisualizer/cytoscape/symbolDependencyVisualizer/index.ts +++ b/packages/app/src/components/DependencyVisualizer/cytoscape/symbolDependencyVisualizer/index.ts @@ -1,13 +1,7 @@ import { - type AuditManifest, - type DependencyManifest, - type Metric, - symbolTypeClass, - symbolTypeEnum, - symbolTypeFunction, - symbolTypeStruct, - symbolTypeVariable, -} from "@stackcore/core/manifest"; + type auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import type { Collection, Core, @@ -69,11 +63,11 @@ export class SymbolDependencyVisualizer { symbolId: string, dependencyDepth: number, dependentDepth: number, - dependencyManifest: DependencyManifest, - auditManifest: AuditManifest, + dependencyManifest: dependencyManifestTypes.DependencyManifest, + auditManifest: auditManifestTypes.AuditManifest, options?: { theme?: "light" | "dark"; - defaultMetric?: Metric | undefined; + defaultMetric?: dependencyManifestTypes.Metric | undefined; onAfterNodeClick?: () => void; onAfterNodeRightClick?: (data: { position: { x: number; y: number }; @@ -205,19 +199,33 @@ export class SymbolDependencyVisualizer { if (!showExternal && data.isExternal) { return true; } - if (!showVariables && data.symbolType === symbolTypeVariable) { + if ( + !showVariables && + data.symbolType === dependencyManifestTypes.symbolTypeVariable + ) { return true; } - if (!showFunctions && data.symbolType === symbolTypeFunction) { + if ( + !showFunctions && + data.symbolType === dependencyManifestTypes.symbolTypeFunction + ) { return true; } - if (!showClasses && data.symbolType === symbolTypeClass) { + if ( + !showClasses && + data.symbolType === dependencyManifestTypes.symbolTypeClass + ) { return true; } - if (!showStructs && data.symbolType === symbolTypeStruct) { + if ( + !showStructs && + data.symbolType === dependencyManifestTypes.symbolTypeStruct + ) { return true; } - if (!showEnums && data.symbolType === symbolTypeEnum) { + if ( + !showEnums && data.symbolType === dependencyManifestTypes.symbolTypeEnum + ) { return true; } return false; diff --git a/packages/app/src/components/DependencyVisualizer/visualizers/FileVisualizer.tsx b/packages/app/src/components/DependencyVisualizer/visualizers/FileVisualizer.tsx index 5579855..91b4a5c 100644 --- a/packages/app/src/components/DependencyVisualizer/visualizers/FileVisualizer.tsx +++ b/packages/app/src/components/DependencyVisualizer/visualizers/FileVisualizer.tsx @@ -4,10 +4,9 @@ import Controls from "../components/controls/Controls.tsx"; import type { VisualizerContext } from "../DependencyVisualizer.tsx"; import { FileDependencyVisualizer } from "../cytoscape/fileDependencyVisualizer/index.ts"; import type { - AuditManifest, - DependencyManifest, - Metric, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import MetricsExtension from "../components/controls/ControlExtensions/MetricsExtension.tsx"; import { useTheme } from "../../../contexts/ThemeProvider.tsx"; import FiltersExtension from "../components/controls/ControlExtensions/FiltersExtension.tsx"; @@ -32,12 +31,16 @@ export default function FileVisualizer( >(undefined); const metricFromUrl = (searchParams.get("metric") || undefined) as - | Metric + | dependencyManifestTypes.Metric | undefined; - const [metric, setMetric] = useState(metricFromUrl); + const [metric, setMetric] = useState< + dependencyManifestTypes.Metric | undefined + >(metricFromUrl); - function handleMetricChange(metric: Metric | undefined) { + function handleMetricChange( + metric: dependencyManifestTypes.Metric | undefined, + ) { if (metric) { searchParams.set("metric", metric); setSearchParams(searchParams); @@ -51,18 +54,23 @@ export default function FileVisualizer( const [contextMenu, setContextMenu] = useState< { position: { x: number; y: number }; - fileDependencyManifest: DependencyManifest[string]; - symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; + fileDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]; + symbolDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]["symbols"][string]; } | undefined >(undefined); const [detailsPane, setDetailsPane] = useState< { manifestId: number; - fileDependencyManifest: DependencyManifest[string]; - symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; - fileAuditManifest: AuditManifest[string]; - symbolAuditManifest: AuditManifest[string]["symbols"][string]; + fileDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]; + symbolDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]["symbols"][string]; + fileAuditManifest: auditManifestTypes.AuditManifest[string]; + symbolAuditManifest: + auditManifestTypes.AuditManifest[string]["symbols"][string]; } | undefined >(undefined); diff --git a/packages/app/src/components/DependencyVisualizer/visualizers/ProjectVisualizer.tsx b/packages/app/src/components/DependencyVisualizer/visualizers/ProjectVisualizer.tsx index 5c8ffe6..b166579 100644 --- a/packages/app/src/components/DependencyVisualizer/visualizers/ProjectVisualizer.tsx +++ b/packages/app/src/components/DependencyVisualizer/visualizers/ProjectVisualizer.tsx @@ -7,10 +7,9 @@ import type { VisualizerContext } from "../DependencyVisualizer.tsx"; import FileDetailsPane from "../components/detailsPanes/FileDetailsPane.tsx"; import { ProjectDependencyVisualizer } from "../cytoscape/projectDependencyVisualizer/index.ts"; import type { - AuditManifest, - DependencyManifest, - Metric, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import { useTheme } from "../../../contexts/ThemeProvider.tsx"; export default function ProjectVisualizer(props: VisualizerContext) { @@ -27,12 +26,16 @@ export default function ProjectVisualizer(props: VisualizerContext) { >(undefined); const metricFromUrl = (searchParams.get("metric") || undefined) as - | Metric + | dependencyManifestTypes.Metric | undefined; - const [metric, setMetric] = useState(metricFromUrl); + const [metric, setMetric] = useState< + dependencyManifestTypes.Metric | undefined + >(metricFromUrl); - function handleMetricChange(metric: Metric | undefined) { + function handleMetricChange( + metric: dependencyManifestTypes.Metric | undefined, + ) { if (metric) { searchParams.set("metric", metric); setSearchParams(searchParams); @@ -46,15 +49,17 @@ export default function ProjectVisualizer(props: VisualizerContext) { const [contextMenu, setContextMenu] = useState< { position: { x: number; y: number }; - fileDependencyManifest: DependencyManifest[string]; + fileDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]; } | undefined >(undefined); const [detailsPane, setDetailsPane] = useState< { manifestId: number; - fileDependencyManifest: DependencyManifest[string]; - fileAuditManifest: AuditManifest[string]; + fileDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]; + fileAuditManifest: auditManifestTypes.AuditManifest[string]; } | undefined >(undefined); diff --git a/packages/app/src/components/DependencyVisualizer/visualizers/SymbolVisualizer.tsx b/packages/app/src/components/DependencyVisualizer/visualizers/SymbolVisualizer.tsx index 325a000..e9f6c80 100644 --- a/packages/app/src/components/DependencyVisualizer/visualizers/SymbolVisualizer.tsx +++ b/packages/app/src/components/DependencyVisualizer/visualizers/SymbolVisualizer.tsx @@ -7,9 +7,9 @@ import type { VisualizerContext } from "../DependencyVisualizer.tsx"; import SymbolDetailsPane from "../components/detailsPanes/SymbolDetailsPane.tsx"; import { useTheme } from "../../../contexts/ThemeProvider.tsx"; import type { - AuditManifest, - DependencyManifest, -} from "@stackcore/core/manifest"; + auditManifestTypes, + dependencyManifestTypes, +} from "@stackcore/shared"; import { SymbolDependencyVisualizer } from "../cytoscape/symbolDependencyVisualizer/index.ts"; export default function SymbolVisualizer( @@ -59,18 +59,23 @@ export default function SymbolVisualizer( const [contextMenu, setContextMenu] = useState< { position: { x: number; y: number }; - fileDependencyManifest: DependencyManifest[string]; - symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; + fileDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]; + symbolDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]["symbols"][string]; } | undefined >(undefined); const [detailsPane, setDetailsPane] = useState< { manifestId: number; - fileDependencyManifest: DependencyManifest[string]; - symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; - fileAuditManifest: AuditManifest[string]; - symbolAuditManifest: AuditManifest[string]["symbols"][string]; + fileDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]; + symbolDependencyManifest: + dependencyManifestTypes.DependencyManifest[string]["symbols"][string]; + fileAuditManifest: auditManifestTypes.AuditManifest[string]; + symbolAuditManifest: + auditManifestTypes.AuditManifest[string]["symbols"][string]; } | undefined >(undefined); // Hook to update highlight node in the graph diff --git a/packages/app/src/contexts/CoreApi.tsx b/packages/app/src/contexts/CoreApi.tsx index d705604..6b81866 100644 --- a/packages/app/src/contexts/CoreApi.tsx +++ b/packages/app/src/contexts/CoreApi.tsx @@ -1,4 +1,13 @@ -import { createContext, useContext, useState } from "react"; +import { createContext, useContext, useEffect, useState } from "react"; +import { Loader, RefreshCw } from "lucide-react"; +import { Button } from "../components/shadcn/Button.tsx"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "../components/shadcn/Card.tsx"; type CoreApiContextType = { isAuthenticated: boolean; @@ -98,6 +107,73 @@ export function CoreApiProvider( return response; } + const [ready, setReady] = useState(false); + const [readyError, setReadyError] = useState(null); + + async function checkAPIReady() { + try { + const response = await handleRequest("/health/liveness", "GET"); + if (response.status !== 200) { + throw new Error("API is not ready"); + } + setReady(true); + } catch (error) { + console.error("Error checking API readiness:", error); + setReadyError(error instanceof Error ? error.message : "Unknown error"); + } + } + + useEffect(() => { + checkAPIReady(); + }, []); + + if (readyError) { + return ( +
+ + + + Something went wrong + + + An unexpected error occurred while connecting to the server. + + + + + + +
+ ); + } + + if (ready === false) { + return ( +
+ + + Connecting to server + + Please wait while we establish a connection... + + + + +

+ This may take a few moments +

+
+
+
+ ); + } + return ( = ( + { children }, +) => { + const { isAuthenticated } = useCoreApi(); + const location = useLocation(); + + if (!isAuthenticated) { + // Redirect to login page with current location as URL parameter + const searchParams = new URLSearchParams(); + searchParams.set("from", location.pathname + location.search); + const redirectUrl = `/login?${searchParams.toString()}`; + return ; + } + + return children; +}; diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx index a208cb4..c4bf1e7 100644 --- a/packages/app/src/pages/index.tsx +++ b/packages/app/src/pages/index.tsx @@ -19,9 +19,9 @@ import { Settings, Users, } from "lucide-react"; -import { ProjectApiTypes } from "@stackcore/core/responses"; +import { projectApiTypes } from "@stackcore/shared"; -type Project = ProjectApiTypes.GetProjectsResponse["results"][number]; +type Project = projectApiTypes.GetProjectsResponse["results"][number]; export default function IndexPage() { const coreApi = useCoreApi(); @@ -48,7 +48,7 @@ export default function IndexPage() { setIsLoadingProjects(true); try { - const { url, method } = ProjectApiTypes.prepareGetProjects({ + const { url, method } = projectApiTypes.prepareGetProjects({ page: 1, limit: 5, workspaceId: selectedWorkspaceId, @@ -57,7 +57,7 @@ export default function IndexPage() { const response = await coreApi.handleRequest(url, method); if (response.ok) { const data = await response - .json() as ProjectApiTypes.GetProjectsResponse; + .json() as projectApiTypes.GetProjectsResponse; setRecentProjects(data.results || []); } } catch (error) { diff --git a/packages/app/src/pages/invitations/claim.tsx b/packages/app/src/pages/invitations/claim.tsx index b7a05db..0a18b62 100644 --- a/packages/app/src/pages/invitations/claim.tsx +++ b/packages/app/src/pages/invitations/claim.tsx @@ -1,5 +1,5 @@ import { Link, useSearchParams } from "react-router"; -import { InvitationApiTypes } from "@stackcore/core/responses"; +import { invitationApiTypes } from "@stackcore/shared"; import { useCoreApi } from "../../contexts/CoreApi.tsx"; import { toast } from "sonner"; import { useEffect, useState } from "react"; @@ -39,14 +39,13 @@ export default function InvitationClaimPage() { try { setClaimState("loading"); - const { url, method, body } = InvitationApiTypes.prepareClaimInvitation( + const { url, method } = invitationApiTypes.prepareClaimInvitation( uuid, ); const response = await coreApi.handleRequest( url, method, - body, ); if (response.ok && response.status === 200) { diff --git a/packages/app/src/pages/login.tsx b/packages/app/src/pages/login.tsx index 19b4df1..af5b9f6 100644 --- a/packages/app/src/pages/login.tsx +++ b/packages/app/src/pages/login.tsx @@ -29,7 +29,7 @@ import { FormLabel, FormMessage, } from "../components/shadcn/Form.tsx"; -import { AuthApiTypes } from "@stackcore/core/responses"; +import { authApiTypes } from "@stackcore/shared"; export default function LoginPage() { const navigate = useNavigate(); @@ -69,7 +69,7 @@ export default function LoginPage() { setIsBusy(true); try { - const { url, method, body } = AuthApiTypes.prepareRequestOtp({ + const { url, method, body } = authApiTypes.prepareRequestOtp({ email: values.email, }); @@ -116,7 +116,7 @@ export default function LoginPage() { url, method, body, - } = AuthApiTypes.prepareVerifyOtp( + } = authApiTypes.prepareVerifyOtp( { email: emailForm.getValues("email"), otp: values.otp }, ); @@ -146,7 +146,7 @@ export default function LoginPage() { throw new Error("Failed to verify one time password"); } - const { token } = await response.json() as AuthApiTypes.VerifyOtpResponse; + const { token } = await response.json() as authApiTypes.VerifyOtpResponse; coreApiContext.login(token); diff --git a/packages/app/src/pages/profile.tsx b/packages/app/src/pages/profile.tsx index 9bbbddf..5401998 100644 --- a/packages/app/src/pages/profile.tsx +++ b/packages/app/src/pages/profile.tsx @@ -50,7 +50,7 @@ import { Copy, Key, Loader, Plus, Trash, User } from "lucide-react"; import { z } from "zod"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { TokenApiTypes } from "@stackcore/core/responses"; +import { tokenApiTypes } from "@stackcore/shared"; type Token = { id: number; @@ -80,7 +80,7 @@ export default function ProfilePage() { const pageSize = pagination.pageSize; try { - const { url, method } = TokenApiTypes.prepareGetTokens({ + const { url, method } = tokenApiTypes.prepareGetTokens({ page, limit: pageSize, }); @@ -91,7 +91,7 @@ export default function ProfilePage() { throw new Error("Failed to get tokens"); } - const data = await response.json() as TokenApiTypes.GetTokensResponse; + const data = await response.json() as tokenApiTypes.GetTokensResponse; setTokens(data.results); setTotal(data.total); @@ -352,7 +352,7 @@ function CreateTokenDialog( async function onSubmit(values: z.infer) { setIsBusy(true); try { - const { url, method, body } = TokenApiTypes.prepareCreateToken({ + const { url, method, body } = tokenApiTypes.prepareCreateToken({ name: values.name, }); @@ -366,7 +366,7 @@ function CreateTokenDialog( throw new Error("Failed to create token"); } - const data = await response.json() as TokenApiTypes.CreateTokenResponse; + const data = await response.json() as tokenApiTypes.CreateTokenResponse; setCreatedToken(data.uuid); toast.success("Token created"); @@ -493,7 +493,7 @@ function DeleteTokenDialog( async function handleDelete() { setIsBusy(true); try { - const { url, method } = TokenApiTypes.prepareDeleteToken(props.token.id); + const { url, method } = tokenApiTypes.prepareDeleteToken(props.token.id); const response = await coreApi.handleRequest(url, method); diff --git a/packages/app/src/pages/projects/add.tsx b/packages/app/src/pages/projects/add.tsx index e1f0819..6536fba 100644 --- a/packages/app/src/pages/projects/add.tsx +++ b/packages/app/src/pages/projects/add.tsx @@ -25,7 +25,7 @@ import { FormLabel, FormMessage, } from "../../components/shadcn/Form.tsx"; -import { ProjectApiTypes } from "@stackcore/core/responses"; +import { projectApiTypes } from "@stackcore/shared"; import { Tabs, TabsContent, @@ -102,7 +102,7 @@ export default function AddProjectPage() { setIsBusy(true); try { - const { url, method, body } = ProjectApiTypes.prepareCreateProject({ + const { url, method, body } = projectApiTypes.prepareCreateProject({ name: values.name, repoUrl: values.repoUrl, workspaceId: selectedWorkspaceId, @@ -139,7 +139,7 @@ export default function AddProjectPage() { } const createResponseBody = await response - .json() as ProjectApiTypes.CreateProjectResponse; + .json() as projectApiTypes.CreateProjectResponse; toast.success("Project created"); diff --git a/packages/app/src/pages/projects/index.tsx b/packages/app/src/pages/projects/index.tsx index 1fd32a8..256b499 100644 --- a/packages/app/src/pages/projects/index.tsx +++ b/packages/app/src/pages/projects/index.tsx @@ -29,7 +29,7 @@ import { import { DataTablePagination } from "../../components/shadcn/Datatablepagination.tsx"; import { toast } from "sonner"; import { PencilRuler, Plus } from "lucide-react"; -import { ProjectApiTypes } from "@stackcore/core/responses"; +import { projectApiTypes } from "@stackcore/shared"; import { Separator } from "../../components/shadcn/Separator.tsx"; type Project = { @@ -79,7 +79,7 @@ export default function WorkspaceProjectsPage() { const pageSize = pagination.pageSize; try { - const { url, method } = ProjectApiTypes.prepareGetProjects({ + const { url, method } = projectApiTypes.prepareGetProjects({ page, limit: pageSize, workspaceId: selectedWorkspaceId, @@ -91,7 +91,7 @@ export default function WorkspaceProjectsPage() { throw new Error("Failed to get projects"); } - const data = await response.json() as ProjectApiTypes.GetProjectsResponse; + const data = await response.json() as projectApiTypes.GetProjectsResponse; setProjects(data.results); setTotal(data.total); diff --git a/packages/app/src/pages/projects/project/base.tsx b/packages/app/src/pages/projects/project/base.tsx index 35924b2..727a244 100644 --- a/packages/app/src/pages/projects/project/base.tsx +++ b/packages/app/src/pages/projects/project/base.tsx @@ -1,12 +1,12 @@ import { Outlet, useNavigate, useParams } from "react-router"; import { useEffect, useState } from "react"; import { Skeleton } from "../../../components/shadcn/Skeleton.tsx"; -import { ProjectApiTypes } from "@stackcore/core/responses"; +import { projectApiTypes } from "@stackcore/shared"; import { toast } from "sonner"; import { useCoreApi } from "../../../contexts/CoreApi.tsx"; export type ProjectPageContext = { - project: ProjectApiTypes.GetProjectDetailsResponse; + project: projectApiTypes.GetProjectDetailsResponse; }; export default function ProjectBase() { @@ -15,7 +15,7 @@ export default function ProjectBase() { const { projectId } = useParams<{ projectId: string }>(); const [project, setProject] = useState< - ProjectApiTypes.GetProjectDetailsResponse | undefined + projectApiTypes.GetProjectDetailsResponse | undefined >(undefined); async function getProject() { @@ -25,7 +25,7 @@ export default function ProjectBase() { try { // Use the new efficient project details endpoint - const { url, method } = ProjectApiTypes.prepareGetProjectDetails( + const { url, method } = projectApiTypes.prepareGetProjectDetails( parseInt(projectId), ); @@ -41,7 +41,7 @@ export default function ProjectBase() { } const projectData = await response - .json() as ProjectApiTypes.GetProjectDetailsResponse; + .json() as projectApiTypes.GetProjectDetailsResponse; setProject(projectData); } catch (error) { console.error(error); diff --git a/packages/app/src/pages/projects/project/manifests/index.tsx b/packages/app/src/pages/projects/project/manifests/index.tsx index a4c9932..b3c16b8 100644 --- a/packages/app/src/pages/projects/project/manifests/index.tsx +++ b/packages/app/src/pages/projects/project/manifests/index.tsx @@ -27,7 +27,7 @@ import { import { DataTablePagination } from "../../../../components/shadcn/Datatablepagination.tsx"; import { toast } from "sonner"; import { Eye, Plus, ScrollText } from "lucide-react"; -import { ManifestApiTypes } from "@stackcore/core/responses"; +import { manifestApiTypes } from "@stackcore/shared"; import { Separator } from "../../../../components/shadcn/Separator.tsx"; import { useCoreApi } from "../../../../contexts/CoreApi.tsx"; import type { ProjectPageContext } from "../base.tsx"; @@ -39,7 +39,7 @@ export default function ProjectManifests() { const [isBusy, setIsBusy] = useState(false); const [manifests, setManifests] = useState< - ManifestApiTypes.GetManifestsResponse["results"] + manifestApiTypes.GetManifestsResponse["results"] >([]); const [total, setTotal] = useState(0); const [search, setSearch] = useState(""); @@ -59,7 +59,7 @@ export default function ProjectManifests() { const pageSize = pagination.pageSize; try { - const { url, method } = ManifestApiTypes.prepareGetManifests({ + const { url, method } = manifestApiTypes.prepareGetManifests({ projectId: context.project.id, page, limit: pageSize, @@ -73,7 +73,7 @@ export default function ProjectManifests() { } const data = await response - .json() as ManifestApiTypes.GetManifestsResponse; + .json() as manifestApiTypes.GetManifestsResponse; setManifests(data.results); setTotal(data.total); @@ -98,7 +98,7 @@ export default function ProjectManifests() { }, [search]); const columns: ColumnDef< - ManifestApiTypes.GetManifestsResponse["results"][number] + manifestApiTypes.GetManifestsResponse["results"][number] >[] = [ { accessorKey: "branch", @@ -160,6 +160,32 @@ export default function ProjectManifests() { id: "actions", header: "Actions", cell: ({ row }) => { + // async function handleClick() { + // const { + // url: smartFilterUrl, + // method: smartFilterMethod, + // body: smartFilterBody, + // } = manifestApiTypes.prepareSmartFilter( + // row.original.id, + // { + // prompt: + // "show me all classes with cyclomatic complexity more than 20", + // }, + // ); + + // await coreApi.handleRequest( + // smartFilterUrl, + // smartFilterMethod, + // smartFilterBody, + // ); + // console.log("clicked", row.original.id); + // } + // return ( + // + // ) + return ( (undefined); const [dependencyManifest, setDependencyManifest] = useState< - DependencyManifest | undefined + dependencyManifestTypes.DependencyManifest | undefined >(undefined); const [auditManifest, setAuditManifest] = useState< - ManifestApiTypes.GetManifestAuditResponse | undefined + manifestApiTypes.GetManifestAuditResponse | undefined >(undefined); async function fetchManifest(url: string) { @@ -35,7 +35,7 @@ export default function ProjectManifest() { throw new Error("Failed to fetch manifest"); } const manifest = await response - .json() as unknown as DependencyManifest; + .json() as unknown as dependencyManifestTypes.DependencyManifest; return manifest; } @@ -53,7 +53,7 @@ export default function ProjectManifest() { const manifestIdNum = parseInt(manifestId); // Fetch manifest details - const { url: manifestUrl, method: manifestMethod } = ManifestApiTypes + const { url: manifestUrl, method: manifestMethod } = manifestApiTypes .prepareGetManifestDetails(manifestIdNum); const manifestResponse = await coreApi.handleRequest( manifestUrl, @@ -65,14 +65,14 @@ export default function ProjectManifest() { } const manifest = await manifestResponse - .json() as ManifestApiTypes.GetManifestDetailsResponse; + .json() as manifestApiTypes.GetManifestDetailsResponse; setManifestData(manifest); const dependencyManifest = await fetchManifest(manifest.manifest); setDependencyManifest(dependencyManifest); // Fetch audit manifest - const { url: auditUrl, method: auditMethod } = ManifestApiTypes + const { url: auditUrl, method: auditMethod } = manifestApiTypes .prepareGetManifestAudit(manifestIdNum); const auditResponse = await coreApi.handleRequest( auditUrl, @@ -107,8 +107,8 @@ export default function ProjectManifest() { return ( ); } diff --git a/packages/app/src/pages/projects/project/settings.tsx b/packages/app/src/pages/projects/project/settings.tsx index d666d6a..9fcc375 100644 --- a/packages/app/src/pages/projects/project/settings.tsx +++ b/packages/app/src/pages/projects/project/settings.tsx @@ -24,7 +24,7 @@ import { FormLabel, FormMessage, } from "../../../components/shadcn/Form.tsx"; -import { ProjectApiTypes } from "@stackcore/core/responses"; +import { projectApiTypes } from "@stackcore/shared"; import { Tabs, TabsContent, @@ -98,7 +98,7 @@ export default function ProjectSettings() { setIsBusy(true); try { - const { url, method, body } = ProjectApiTypes.prepareUpdateProject( + const { url, method, body } = projectApiTypes.prepareUpdateProject( context.project.id, { name: values.name, diff --git a/packages/app/src/pages/workspaces/add.tsx b/packages/app/src/pages/workspaces/add.tsx index 745d108..e664759 100644 --- a/packages/app/src/pages/workspaces/add.tsx +++ b/packages/app/src/pages/workspaces/add.tsx @@ -24,7 +24,7 @@ import { FormLabel, FormMessage, } from "../../components/shadcn/Form.tsx"; -import { WorkspaceApiTypes } from "@stackcore/core/responses"; +import { workspaceApiTypes } from "@stackcore/shared"; export default function AddWorkspacePage() { const navigate = useNavigate(); @@ -46,7 +46,7 @@ export default function AddWorkspacePage() { setIsBusy(true); try { - const { url, method, body } = WorkspaceApiTypes + const { url, method, body } = workspaceApiTypes .prepareCreateWorkspace({ name: values.name, }); @@ -69,7 +69,7 @@ export default function AddWorkspacePage() { toast.success("Workspace created"); const workspace = await response - .json() as WorkspaceApiTypes.CreateWorkspaceResponse; + .json() as workspaceApiTypes.CreateWorkspaceResponse; await refreshWorkspaces(); diff --git a/packages/app/src/pages/workspaces/workspace/index.tsx b/packages/app/src/pages/workspaces/workspace/index.tsx index 7b51f40..f5c19cf 100644 --- a/packages/app/src/pages/workspaces/workspace/index.tsx +++ b/packages/app/src/pages/workspaces/workspace/index.tsx @@ -14,7 +14,7 @@ import { } from "../../../components/shadcn/Card.tsx"; import { Badge } from "../../../components/shadcn/Badge.tsx"; import { Skeleton } from "../../../components/shadcn/Skeleton.tsx"; -import { MemberApiTypes, WorkspaceApiTypes } from "@stackcore/core/responses"; +import { memberTypes, workspaceApiTypes } from "@stackcore/shared"; import { zodResolver } from "@hookform/resolvers/zod"; import { Loader, Trash } from "lucide-react"; import { Button } from "../../../components/shadcn/Button.tsx"; @@ -117,7 +117,7 @@ export default function WorkspacePage() { )} {(workspace && - workspace.role === MemberApiTypes.ADMIN_ROLE && + workspace.role === memberTypes.ADMIN_ROLE && workspace.isTeam) && ( {row.original.role} - {(context.workspace.role === MemberApiTypes.ADMIN_ROLE && + {(context.workspace.role === memberTypes.ADMIN_ROLE && row.original.email !== coreApi.getUserFromToken()?.email) && ( - {context.workspace.role === MemberApiTypes.ADMIN_ROLE && ( + {context.workspace.role === memberTypes.ADMIN_ROLE && ( - + admin - + member diff --git a/packages/app/src/pages/workspaces/workspace/subscription.tsx b/packages/app/src/pages/workspaces/workspace/subscription.tsx index d26c198..98dbefc 100644 --- a/packages/app/src/pages/workspaces/workspace/subscription.tsx +++ b/packages/app/src/pages/workspaces/workspace/subscription.tsx @@ -9,11 +9,7 @@ import { CardTitle, } from "../../../components/shadcn/Card.tsx"; import { Button } from "../../../components/shadcn/Button.tsx"; -import { - BillingApiTypes, - MemberApiTypes, - WorkspaceApiTypes, -} from "@stackcore/core/responses"; +import { billingApiTypes, memberTypes, stripeTypes } from "@stackcore/shared"; import { Check, CreditCard, Loader } from "lucide-react"; import { Dialog, @@ -49,12 +45,12 @@ export default function WorkspaceSubscription() { const coreApi = useCoreApi(); const [subscription, setSubscription] = useState< - BillingApiTypes.SubscriptionDetails | undefined + billingApiTypes.SubscriptionDetails | undefined >(undefined); const [billingCycle, setBillingCycle] = useState< - WorkspaceApiTypes.StripeBillingCycle + stripeTypes.StripeBillingCycle >( - WorkspaceApiTypes.YEARLY_BILLING_CYCLE, + stripeTypes.YEARLY_BILLING_CYCLE, ); useEffect(() => { @@ -63,7 +59,7 @@ export default function WorkspaceSubscription() { async function getSubscription(workspaceId: number) { try { - const { url, method } = BillingApiTypes.prepareGetSubscription( + const { url, method } = billingApiTypes.prepareGetSubscription( workspaceId, ); @@ -73,7 +69,7 @@ export default function WorkspaceSubscription() { throw new Error("Failed to get subscription"); } - const data = await response.json() as BillingApiTypes.SubscriptionDetails; + const data = await response.json() as billingApiTypes.SubscriptionDetails; setSubscription(data); } catch (error) { @@ -88,7 +84,7 @@ export default function WorkspaceSubscription() { setStripePortalBusy(true); try { - const { url, method, body } = BillingApiTypes.prepareCreatePortalSession({ + const { url, method, body } = billingApiTypes.prepareCreatePortalSession({ workspaceId: context.workspace.id, returnUrl: globalThis.location.href, }); @@ -119,7 +115,7 @@ export default function WorkspaceSubscription() { setStripePortalPaymentMethodBusy(true); try { - const { url, method, body } = BillingApiTypes + const { url, method, body } = billingApiTypes .prepareCreatePortalSessionPaymentMethod({ workspaceId: context.workspace.id, returnUrl: globalThis.location.href, @@ -145,26 +141,25 @@ export default function WorkspaceSubscription() { } function getChangeType( - currentProduct: WorkspaceApiTypes.StripeProduct, - currentBillingCycle: WorkspaceApiTypes.StripeBillingCycle | null, - newProduct: WorkspaceApiTypes.StripeProduct, - newBillingCycle: WorkspaceApiTypes.StripeBillingCycle, + currentProduct: stripeTypes.StripeProduct, + currentBillingCycle: stripeTypes.StripeBillingCycle | null, + newProduct: stripeTypes.StripeProduct, + newBillingCycle: stripeTypes.StripeBillingCycle, ): "upgrade" | "downgrade" | "same" | "custom" { if ( - currentProduct === WorkspaceApiTypes.CUSTOM_PRODUCT || - newProduct === WorkspaceApiTypes.CUSTOM_PRODUCT || + currentProduct === stripeTypes.CUSTOM_PRODUCT || + newProduct === stripeTypes.CUSTOM_PRODUCT || currentBillingCycle === null ) { return "custom"; } // Check if changing to a higher tier product - const isUpgradingProduct = - (currentProduct === WorkspaceApiTypes.BASIC_PRODUCT && - [WorkspaceApiTypes.PRO_PRODUCT, WorkspaceApiTypes.PREMIUM_PRODUCT] - .includes(newProduct)) || - (currentProduct === WorkspaceApiTypes.PRO_PRODUCT && - newProduct === WorkspaceApiTypes.PREMIUM_PRODUCT); + const isUpgradingProduct = (currentProduct === stripeTypes.BASIC_PRODUCT && + [stripeTypes.PRO_PRODUCT, stripeTypes.PREMIUM_PRODUCT] + .includes(newProduct)) || + (currentProduct === stripeTypes.PRO_PRODUCT && + newProduct === stripeTypes.PREMIUM_PRODUCT); // If products are different, determine if upgrade or downgrade if (currentProduct !== newProduct) { @@ -174,8 +169,8 @@ export default function WorkspaceSubscription() { // If only billing cycle changed, yearly is an upgrade from monthly if (currentBillingCycle !== newBillingCycle) { const isUpgradingBilling = - currentBillingCycle === WorkspaceApiTypes.MONTHLY_BILLING_CYCLE && - newBillingCycle === WorkspaceApiTypes.YEARLY_BILLING_CYCLE; + currentBillingCycle === stripeTypes.MONTHLY_BILLING_CYCLE && + newBillingCycle === stripeTypes.YEARLY_BILLING_CYCLE; return isUpgradingBilling ? "upgrade" : "downgrade"; } @@ -228,7 +223,7 @@ export default function WorkspaceSubscription() { Access disabled. Update your payment method )} - {context.workspace.role === MemberApiTypes.ADMIN_ROLE && ( + {context.workspace.role === memberTypes.ADMIN_ROLE && (
{billingCycle === - WorkspaceApiTypes.MONTHLY_BILLING_CYCLE + stripeTypes.MONTHLY_BILLING_CYCLE ? (
@@ -457,7 +452,7 @@ export default function WorkspaceSubscription() { @@ -529,15 +524,15 @@ export default function WorkspaceSubscription() { function SubscriptionCard(props: { workspaceId: number; - currentSubscription: BillingApiTypes.SubscriptionDetails; - product: WorkspaceApiTypes.StripeProduct; + currentSubscription: billingApiTypes.SubscriptionDetails; + product: stripeTypes.StripeProduct; title: string; description: string; features: string[]; subscriptionPrice: string; - billingCycle: WorkspaceApiTypes.StripeBillingCycle; + billingCycle: stripeTypes.StripeBillingCycle; changeType: "upgrade" | "downgrade" | "same" | "custom"; - role: MemberApiTypes.MemberRole | null; + role: memberTypes.MemberRole | null; }) { return ( @@ -578,7 +573,7 @@ function SubscriptionCard(props: { ) - : props.role === MemberApiTypes.MEMBER_ROLE + : props.role === memberTypes.MEMBER_ROLE ? (