diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/status-indicator.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/status-indicator.tsx index cae4486718..2465aa2ef0 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/status-indicator.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/status-indicator.tsx @@ -60,28 +60,46 @@ export function StatusIndicator({ {withSignal && !isLoading && (
- {[0, 0.15, 0.3, 0.45].map((delay, index) => ( -
- key={index} - className={cn( - "absolute inset-0 size-2 rounded-full", - pulseColors[index], - index === 0 && "opacity-75", - index === 1 && "opacity-60", - index === 2 && "opacity-40", - index === 3 && "opacity-25", - )} - style={{ - animation: "ping 2s cubic-bezier(0, 0, 0.2, 1) infinite", - animationDelay: `${delay}s`, - }} - /> - ))} -
+
)}
); } + +type PulseIndicatorProps = { + colors?: string[]; + coreColor?: string; + className?: string; +}; + +export function PulseIndicator({ + colors = ["bg-successA-9", "bg-successA-10", "bg-successA-11", "bg-successA-12"], + coreColor = "bg-successA-9", + className, +}: PulseIndicatorProps) { + return ( +
+ {[0, 0.15, 0.3, 0.45].map((delay, index) => ( +
+ ))} +
+
+ ); +} diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/components/deployment-select.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/components/deployment-select.tsx new file mode 100644 index 0000000000..d27872cfe7 --- /dev/null +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/components/deployment-select.tsx @@ -0,0 +1,107 @@ +"use client"; +import type { Deployment } from "@/lib/collections"; +import { shortenId } from "@/lib/shorten-id"; +import { + InfoTooltip, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@unkey/ui"; +import { format } from "date-fns"; +import { PulseIndicator } from "../../details/active-deployment-card/status-indicator"; +import { useProjectLayout } from "../../layout-provider"; + +type DeploymentSelectProps = { + value: string; + onValueChange: (value: string) => void; + deployments: Array<{ + deployment: Deployment; + }>; + isLoading: boolean; + placeholder?: string; + disabled?: boolean; + id?: string; + disabledDeploymentId?: string; +}; + +export function DeploymentSelect({ + value, + onValueChange, + deployments, + isLoading, + placeholder = "Select deployment", + disabled = false, + id, + disabledDeploymentId, +}: DeploymentSelectProps) { + const { liveDeploymentId } = useProjectLayout(); + const renderOptions = () => { + if (isLoading) { + return ( + + Loading... + + ); + } + if (deployments.length === 0) { + return ( + + No deployments found + + ); + } + + return deployments.map(({ deployment }) => { + const isDisabled = deployment.id === disabledDeploymentId || !deployment.hasOpenApiSpec; + const deployedAt = format(deployment.createdAt, "MMM d, h:mm a"); + + return ( + + +
+ {shortenId(deployment.id)} + + {deployedAt} + {deployment.id === liveDeploymentId && } +
+
+
+ ); + }); + }; + + return ( + + ); +} diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/deployment-select.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/deployment-select.tsx deleted file mode 100644 index 8c0bba0f98..0000000000 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/deployment-select.tsx +++ /dev/null @@ -1,85 +0,0 @@ -"use client"; -import type { Deployment } from "@/lib/collections"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@unkey/ui"; -import { format } from "date-fns"; - -type DeploymentSelectProps = { - value: string; - onValueChange: (value: string) => void; - deployments: Array<{ - deployment: Deployment; - }>; - isLoading: boolean; - placeholder?: string; - disabled?: boolean; - id?: string; - disabledDeploymentId?: string; -}; - -export function DeploymentSelect({ - value, - onValueChange, - deployments, - isLoading, - placeholder = "Select deployment", - disabled = false, - id, - disabledDeploymentId, -}: DeploymentSelectProps) { - const renderOptions = () => { - if (isLoading) { - return ( - - Loading... - - ); - } - if (deployments.length === 0) { - return ( - - No deployments found - - ); - } - - return deployments.map(({ deployment }) => { - const isDisabled = deployment.id === disabledDeploymentId; - const deployedAt = format(deployment.createdAt, "MMM d, h:mm a"); - const commitMessage = deployment.gitCommitMessage?.trim(); - const displayMessage = commitMessage || deployment.gitBranch; - const truncatedMessage = - displayMessage.length > 50 ? `${displayMessage.substring(0, 50)}...` : displayMessage; - const shortSha = deployment.gitCommitSha?.substring(0, 7) || deployment.id.substring(0, 7); - - return ( - -
- {truncatedMessage} - - {deployedAt} - - {shortSha} -
-
- ); - }); - }; - - return ( - - ); -} diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/page.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/page.tsx index 9eabf72c38..7961350a4a 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/page.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/openapi-diff/page.tsx @@ -10,7 +10,7 @@ import { useCallback, useEffect, useState } from "react"; import { Card } from "../details/card"; import { useProjectLayout } from "../layout-provider"; import { DiffViewerContent } from "./components/client"; -import { DeploymentSelect } from "./deployment-select"; +import { DeploymentSelect } from "./components/deployment-select"; export default function DiffPage() { const { collections, isDetailsOpen, liveDeploymentId } = useProjectLayout(); diff --git a/apps/dashboard/lib/collections/deploy/deployments.ts b/apps/dashboard/lib/collections/deploy/deployments.ts index 3fa9c178c1..81e8d39dd3 100644 --- a/apps/dashboard/lib/collections/deploy/deployments.ts +++ b/apps/dashboard/lib/collections/deploy/deployments.ts @@ -14,6 +14,8 @@ const schema = z.object({ gitCommitAuthorHandle: z.string().nullable(), gitCommitAuthorAvatarUrl: z.string(), gitCommitTimestamp: z.number().int().nullable(), + // OpenAPI + hasOpenApiSpec: z.boolean(), // Immutable configuration snapshot runtimeConfig: z.object({ regions: z.array( diff --git a/apps/dashboard/lib/trpc/routers/deploy/deployment/list.ts b/apps/dashboard/lib/trpc/routers/deploy/deployment/list.ts index df2c6b237b..4187570b62 100644 --- a/apps/dashboard/lib/trpc/routers/deploy/deployment/list.ts +++ b/apps/dashboard/lib/trpc/routers/deploy/deployment/list.ts @@ -9,7 +9,6 @@ export const listDeployments = t.procedure .input(z.object({ projectId: z.string() })) .query(async ({ ctx, input }) => { try { - // Get all deployments for this workspace and specific project const deployments = await db.query.deployments.findMany({ where: (table, { eq, and }) => and(eq(table.workspaceId, ctx.workspace.id), eq(table.projectId, input.projectId)), @@ -25,16 +24,18 @@ export const listDeployments = t.procedure gitCommitTimestamp: true, runtimeConfig: true, status: true, + openapiSpec: true, createdAt: true, }, limit: 500, }); - return deployments.map((deployment) => ({ + return deployments.map(({ openapiSpec, ...deployment }) => ({ ...deployment, gitBranch: deployment.gitBranch ?? "main", gitCommitAuthorAvatarUrl: deployment.gitCommitAuthorAvatarUrl ?? "https://github.com/identicons/dummy-user.png", + hasOpenApiSpec: Boolean(openapiSpec), gitCommitTimestamp: deployment.gitCommitTimestamp, })); } catch (_error) {