diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/RunDetails.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/RunDetails.tsx index 7f46d6dd..bb02a679 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/RunDetails.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/RunDetails.tsx @@ -5,7 +5,7 @@ import { Tables } from "@/lib/database.types"; import { motion } from "framer-motion"; import { RunsTable } from "./RunsTable"; -export default function RunDetails({ runs, project }: { runs: Tables<'digger_runs'>[], project: Tables<'projects'> }) { +export default function RunsDetails({ runs, project }: { runs: Tables<'digger_runs'>[], project: Tables<'projects'> }) { return ( - + diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/RunsTable.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/RunsTable.tsx index ed557536..dda58c42 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/RunsTable.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/RunsTable.tsx @@ -1,14 +1,15 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Tables } from "@/lib/database.types"; -import { motion } from "framer-motion"; +import { AnimatePresence, motion } from "framer-motion"; import { Activity } from "lucide-react"; import moment from "moment"; +import Link from "next/link"; -type StatusColor = { +export type StatusColor = { [key: string]: string; }; -const statusColors: StatusColor = { +export const statusColors: StatusColor = { queued: 'bg-yellow-200/50 text-yellow-800 dark:bg-yellow-900/50 dark:text-yellow-200', 'pending_approval': 'bg-blue-200/50 text-blue-800 dark:bg-blue-900/50 dark:text-blue-200', running: 'bg-purple-200/50 text-purple-800 dark:bg-purple-900/50 dark:text-purple-200', @@ -17,56 +18,81 @@ const statusColors: StatusColor = { failed: 'bg-red-200/50 text-red-800 dark:bg-red-900/50 dark:text-red-200', }; -export const RunsTable = ({ runs }: { runs: Tables<'digger_runs'>[] }) => ( - - - - Run ID - Commit ID - Status - Date - User - - - - {runs.length > 0 ? ( - runs.map((run) => ( - - {run.id.length > 8 ? `${run.id.substring(0, 8)}...` : run.id} - {run.commit_id} - - - {run.status.toUpperCase()} - - - {moment(run.created_at).fromNow()} - {run.approval_author} - - )) - ) : ( +const statusOrder = ['running', 'queued', 'pending_approval', 'approved', 'succeeded', 'failed']; + +export const RunsTable = ({ runs, projectSlug }: { runs: Tables<'digger_runs'>[], projectSlug: string }) => { + const sortedRuns = [...runs].sort((a, b) => { + const statusA = statusOrder.indexOf(a.status.toLowerCase()); + const statusB = statusOrder.indexOf(b.status.toLowerCase()); + if (statusA !== statusB) return statusA - statusB; + return moment(b.created_at).valueOf() - moment(a.created_at).valueOf(); + }); + + return ( +
+ - - - - - -

No runs available

-

- Runs will appear here once they are initiated. -

-
-
+ Run ID + Commit ID + Status + Date + User
- )} - -
-); \ No newline at end of file + + + + {sortedRuns.length > 0 ? ( + sortedRuns.map((run) => ( + + + + + {run.id.length > 8 ? `${run.id.substring(0, 8)}...` : run.id} + + + + {run.commit_id} + + + {run.status.toUpperCase()} + + + {moment(run.created_at).fromNow()} + {run.approval_author} + + )) + ) : ( + + + + + + +

No runs available

+

+ Runs will appear here once they are initiated. +

+
+
+
+ )} +
+
+ + ); +}; \ No newline at end of file diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/layout.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/layout.tsx index 0bab2001..48c622b9 100644 --- a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/layout.tsx +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/layout.tsx @@ -1,10 +1,8 @@ -import { ApplicationLayoutShell } from '@/components/ApplicationLayoutShell'; -import { InternalNavbar } from '@/components/NavigationMenu/InternalNavbar'; -import { PageHeading } from '@/components/PageHeading'; -import { TabsNavigationV2 } from '@/components/TabsNavigation/TabsNavigation'; -import { getProjectTitleById, getSlimProjectBySlug } from '@/data/user/projects'; -import { projectSlugParamSchema } from '@/utils/zod-schemas/params'; -import { Suspense, type ReactNode } from 'react'; +import { PageHeading } from "@/components/PageHeading"; +import { TabsNavigationV2 } from "@/components/TabsNavigation/TabsNavigation"; +import { getProjectTitleById, getSlimProjectBySlug } from "@/data/user/projects"; +import { projectSlugParamSchema } from "@/utils/zod-schemas/params"; +import { Suspense } from "react"; async function ProjectPageHeading({ projectId }: { projectId: string }) { @@ -16,18 +14,7 @@ async function ProjectPageHeading({ projectId }: { projectId: string }) { /> ); } - -export default async function ProjectLayout({ - params, - children, - navbar, - sidebar, -}: { - children: ReactNode; - params: unknown; - navbar: ReactNode; - sidebar: ReactNode; -}) { +export default async function ProjectPagesLayout({ params, children }: { params: unknown, children: React.ReactNode }) { const { projectSlug } = projectSlugParamSchema.parse(params); const project = await getSlimProjectBySlug(projectSlug); @@ -45,28 +32,16 @@ export default async function ProjectLayout({ href: `/project/${projectSlug}/settings`, }, ]; + return <> +
+ + + + + + +
+ {children} + - return ( - - -
- -
- {navbar} -
-
-
-
- - - - - - -
- {children} -
-
-
- ); -} +} \ No newline at end of file diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@navbar/default.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@navbar/default.tsx similarity index 100% rename from src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@navbar/default.tsx rename to src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@navbar/default.tsx diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@navbar/loading.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@navbar/loading.tsx similarity index 100% rename from src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@navbar/loading.tsx rename to src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@navbar/loading.tsx diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@navbar/settings/page.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@navbar/settings/page.tsx similarity index 100% rename from src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@navbar/settings/page.tsx rename to src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@navbar/settings/page.tsx diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@sidebar/ProjectSidebar.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@sidebar/ProjectSidebar.tsx similarity index 100% rename from src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@sidebar/ProjectSidebar.tsx rename to src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@sidebar/ProjectSidebar.tsx diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@sidebar/default.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@sidebar/default.tsx similarity index 100% rename from src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/@sidebar/default.tsx rename to src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/@sidebar/default.tsx diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/layout.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/layout.tsx new file mode 100644 index 00000000..db06528e --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/layout.tsx @@ -0,0 +1,33 @@ +import { ApplicationLayoutShell } from '@/components/ApplicationLayoutShell'; +import { InternalNavbar } from '@/components/NavigationMenu/InternalNavbar'; +import { Suspense, type ReactNode } from 'react'; + + +export default async function ProjectLayout({ + params, + children, + navbar, + sidebar, +}: { + children: ReactNode; + params: unknown; + navbar: ReactNode; + sidebar: ReactNode; +}) { + + return ( + + +
+ +
+ {navbar} +
+
+
+ {children} +
+
+
+ ); +} diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/runs/[runId]/RunDetails.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/runs/[runId]/RunDetails.tsx new file mode 100644 index 00000000..e60583ac --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/runs/[runId]/RunDetails.tsx @@ -0,0 +1,80 @@ +'use client'; + +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tables } from "@/lib/database.types"; +import { AnimatePresence, motion } from 'framer-motion'; +import { ChevronDown } from 'lucide-react'; +import React, { useState } from 'react'; +import { statusColors } from "../../(specific-project-pages)/RunsTable"; + +export const RunDetails: React.FC<{ run: Tables<'digger_runs'> }> = ({ run }) => { + const [isExpanded, setIsExpanded] = useState(false); + + return ( + + + + + Run {run.id} + + + {run.status.toUpperCase()} + + + +
+
+

Commit: {run.commit_id}

+

Date: {run.created_at}

+
+ +
+ + {isExpanded && run.status && ( + +

Plan Summary

+
+
+ {run.status} + Created +
+
+ {run.status} + Updated +
+
+ {run.status} + Deleted +
+
+
+ )} +
+
+
+
+ ); +}; diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/runs/[runId]/page.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/runs/[runId]/page.tsx new file mode 100644 index 00000000..bd604628 --- /dev/null +++ b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/runs/[runId]/page.tsx @@ -0,0 +1,53 @@ + +import { PageHeading } from "@/components/PageHeading"; +import { T } from "@/components/ui/Typography"; +import { getRunById } from "@/data/user/runs"; +import { + runIdParamSchema +} from "@/utils/zod-schemas/params"; +import type { Metadata } from "next"; +import { Suspense } from "react"; +import { RunDetails } from "./RunDetails"; + +export const metadata: Metadata = { + title: "Projects", + description: "You can create projects within teams, or within your organization.", +}; + +type RunDetailPageProps = { + params: { + runId: string; + }; +}; + + +export default async function RunDetailPage({ + params, + +}: RunDetailPageProps) { + const { runId } = runIdParamSchema.parse(params); + const run = await getRunById(runId); + + return ( +
+ +
+ +
+ { + + Loading run details... + + } + > + + + } +
+ ); +} diff --git a/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/runs/page.tsx b/src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/runs/page.tsx similarity index 100% rename from src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/(specific-project-pages)/runs/page.tsx rename to src/app/(dynamic-pages)/(authenticated-pages)/(application-pages)/project/[projectSlug]/runs/page.tsx diff --git a/src/utils/zod-schemas/params.ts b/src/utils/zod-schemas/params.ts index cff995bf..d8956607 100644 --- a/src/utils/zod-schemas/params.ts +++ b/src/utils/zod-schemas/params.ts @@ -20,3 +20,7 @@ export const projectParamSchema = z.object({ export const projectSlugParamSchema = z.object({ projectSlug: z.string(), }); + +export const runIdParamSchema = z.object({ + runId: z.string().uuid(), +}); diff --git a/supabase/migrations/20240726134603_run_approval_flow.sql b/supabase/migrations/20240726134603_run_approval_flow.sql new file mode 100644 index 00000000..4ae5166d --- /dev/null +++ b/supabase/migrations/20240726134603_run_approval_flow.sql @@ -0,0 +1,16 @@ +-- Create a new enum type for approval_status +CREATE TYPE digger_run_approval_status AS ENUM ( + 'pending_approval', + 'approved', + 'running', + 'queued', + 'succeeded', + 'failed' +); + +-- Add the approval_status column to the digger_runs table +ALTER TABLE "public"."digger_runs" +ADD COLUMN "approval_status" digger_run_approval_status NOT NULL DEFAULT 'pending_approval'; + +-- Create an index on the new column for better query performance +CREATE INDEX idx_digger_runs_approval_status ON "public"."digger_runs" ("approval_status"); \ No newline at end of file