From 7fa0320d913bf17de612481ecfd59177b58395c1 Mon Sep 17 00:00:00 2001 From: Harshith Mullapudi Date: Thu, 17 Jul 2025 13:10:29 +0530 Subject: [PATCH] Feat: now you can delete a episode --- .../app/components/logs/log-text-collapse.tsx | 166 ++++++++++++++++++ .../app/components/logs/virtual-logs-list.tsx | 99 +---------- .../webapp/app/components/ui/alert-dialog.tsx | 139 +++++++++++++++ apps/webapp/app/hooks/use-logs.tsx | 2 + .../app/routes/api.v1.episode.delete.tsx | 4 +- .../app/routes/api.v1.logs.activity.tsx | 2 + apps/webapp/app/routes/api.v1.logs.all.tsx | 3 + 7 files changed, 322 insertions(+), 93 deletions(-) create mode 100644 apps/webapp/app/components/logs/log-text-collapse.tsx create mode 100644 apps/webapp/app/components/ui/alert-dialog.tsx diff --git a/apps/webapp/app/components/logs/log-text-collapse.tsx b/apps/webapp/app/components/logs/log-text-collapse.tsx new file mode 100644 index 0000000..f5bdff4 --- /dev/null +++ b/apps/webapp/app/components/logs/log-text-collapse.tsx @@ -0,0 +1,166 @@ +import { useState } from "react"; +import { useFetcher } from "@remix-run/react"; +import { AlertCircle, Info, Trash } from "lucide-react"; +import { cn } from "~/lib/utils"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"; +import { Button } from "../ui"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "../ui/alert-dialog"; + +interface LogTextCollapseProps { + text?: string; + error?: string; + logData: any; + episodeUUID?: string; +} + +export function LogTextCollapse({ + episodeUUID, + text, + error, + logData, +}: LogTextCollapseProps) { + const [dialogOpen, setDialogOpen] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const deleteFetcher = useFetcher(); + + const handleDelete = () => { + console.log(logData); + if (!episodeUUID) { + console.error("No episodeUuid found in log data"); + return; + } + + deleteFetcher.submit( + { episodeUuid: episodeUUID }, + { + method: "DELETE", + action: "/api/v1/episode/delete", + encType: "application/json", + }, + ); + setDeleteDialogOpen(false); + }; + + // Show collapse if text is long (by word count) + const COLLAPSE_WORD_LIMIT = 30; + + if (!text) { + return ( +
+ No log details. +
+ ); + } + + // Split by words for word count + const words = text.split(/\s+/); + const isLong = words.length > COLLAPSE_WORD_LIMIT; + + let displayText: string; + if (isLong) { + displayText = words.slice(0, COLLAPSE_WORD_LIMIT).join(" ") + " ..."; + } else { + displayText = text; + } + + return ( + <> +
+

+ {displayText} +

+ {isLong && ( + <> + + + + + Log Details + + +
+

+ {text} +

+
+
+
+ + )} +
+
+ {isLong && ( +
+ + {episodeUUID && ( + + + + + + + Delete Episode + + Are you sure you want to delete this episode? This action + cannot be undone. + + + + Cancel + + Continue + + + + + )} +
+ )} + {error && ( +
+ + + {error} + +
+ )} +
+ + ); +} diff --git a/apps/webapp/app/components/logs/virtual-logs-list.tsx b/apps/webapp/app/components/logs/virtual-logs-list.tsx index ab3bf42..be9484a 100644 --- a/apps/webapp/app/components/logs/virtual-logs-list.tsx +++ b/apps/webapp/app/components/logs/virtual-logs-list.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef } from "react"; import { InfiniteLoader, AutoSizer, @@ -10,96 +10,9 @@ import { import { type LogItem } from "~/hooks/use-logs"; import { Badge } from "~/components/ui/badge"; import { Card, CardContent } from "~/components/ui/card"; -import { AlertCircle } from "lucide-react"; import { cn } from "~/lib/utils"; import { ScrollManagedList } from "../virtualized-list"; - -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog"; -import { Button } from "../ui"; - -// --- LogTextCollapse component --- -function LogTextCollapse({ text, error }: { text?: string; error?: string }) { - const [dialogOpen, setDialogOpen] = useState(false); - - // Show collapse if text is long (by word count) - const COLLAPSE_WORD_LIMIT = 30; - - if (!text) { - return ( -
- No log details. -
- ); - } - - // Split by words for word count - const words = text.split(/\s+/); - const isLong = words.length > COLLAPSE_WORD_LIMIT; - - let displayText: string; - if (isLong) { - displayText = words.slice(0, COLLAPSE_WORD_LIMIT).join(" ") + " ..."; - } else { - displayText = text; - } - - return ( - <> -
-

- {displayText} -

- {isLong && ( - <> - - - - - Log Details - - -
-

- {text} -

-
-
-
- - )} -
-
- {isLong && ( - - )} - {error && ( -
- - - {error} - -
- )} -
- - ); -} +import { LogTextCollapse } from "./log-text-collapse"; interface VirtualLogsListProps { logs: LogItem[]; @@ -183,7 +96,12 @@ function LogItemRenderer( - + @@ -196,7 +114,6 @@ export function VirtualLogsList({ hasMore, loadMore, isLoading, - height = 600, }: VirtualLogsListProps) { // Create a CellMeasurerCache instance using useRef to prevent recreation const cacheRef = useRef(null); diff --git a/apps/webapp/app/components/ui/alert-dialog.tsx b/apps/webapp/app/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..7a6229c --- /dev/null +++ b/apps/webapp/app/components/ui/alert-dialog.tsx @@ -0,0 +1,139 @@ +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; +import React from "react"; + +import { buttonVariants } from "./button"; +import { cn } from "../../lib/utils"; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = "AlertDialogHeader"; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = "AlertDialogFooter"; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/apps/webapp/app/hooks/use-logs.tsx b/apps/webapp/app/hooks/use-logs.tsx index 5156d22..16d6749 100644 --- a/apps/webapp/app/hooks/use-logs.tsx +++ b/apps/webapp/app/hooks/use-logs.tsx @@ -12,6 +12,8 @@ export interface LogItem { sourceURL?: string; integrationSlug?: string; activityId?: string; + episodeUUID?: string; + data?: any; } export interface LogsResponse { diff --git a/apps/webapp/app/routes/api.v1.episode.delete.tsx b/apps/webapp/app/routes/api.v1.episode.delete.tsx index 55dff0c..94c5ef0 100644 --- a/apps/webapp/app/routes/api.v1.episode.delete.tsx +++ b/apps/webapp/app/routes/api.v1.episode.delete.tsx @@ -1,13 +1,13 @@ import { z } from "zod"; import { json } from "@remix-run/node"; -import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; +import { createHybridActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { deleteEpisodeWithRelatedNodes } from "~/services/graphModels/episode"; export const DeleteEpisodeBodyRequest = z.object({ episodeUuid: z.string().uuid("Episode UUID must be a valid UUID"), }); -const { action, loader } = createActionApiRoute( +const { action, loader } = createHybridActionApiRoute( { body: DeleteEpisodeBodyRequest, allowJWT: true, diff --git a/apps/webapp/app/routes/api.v1.logs.activity.tsx b/apps/webapp/app/routes/api.v1.logs.activity.tsx index 712c519..fd8301f 100644 --- a/apps/webapp/app/routes/api.v1.logs.activity.tsx +++ b/apps/webapp/app/routes/api.v1.logs.activity.tsx @@ -116,10 +116,12 @@ export async function loader({ request }: LoaderFunctionArgs) { processedAt: log.processedAt, status: log.status, error: log.error, + episodeUUID: (log.output as any)?.episodeUuid, sourceURL: log.activity?.sourceURL, integrationSlug: log.activity?.integrationAccount?.integrationDefinition?.slug, activityId: log.activityId, + data: log.data, })); return json({ diff --git a/apps/webapp/app/routes/api.v1.logs.all.tsx b/apps/webapp/app/routes/api.v1.logs.all.tsx index f58214b..35a1da2 100644 --- a/apps/webapp/app/routes/api.v1.logs.all.tsx +++ b/apps/webapp/app/routes/api.v1.logs.all.tsx @@ -60,6 +60,7 @@ export async function loader({ request }: LoaderFunctionArgs) { processedAt: true, status: true, error: true, + output: true, data: true, activity: { select: { @@ -120,10 +121,12 @@ export async function loader({ request }: LoaderFunctionArgs) { "No content", time: log.createdAt, processedAt: log.processedAt, + episodeUUID: (log.output as any)?.episodeUuid, status: log.status, error: log.error, sourceURL: log.activity?.sourceURL, integrationSlug: integrationDef?.slug, + data: log.data, }; });