Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { ReservedMetadataKey } from "@ctrlplane/validators/conditions";

import { urls } from "~/app/urls";
import { api } from "~/trpc/react";
import { VersionDependencyBadge } from "./policy-evaluations/VersionDependencyBadge";

type PolicyEvaluation = RouterOutputs["policy"]["evaluate"]["releaseTarget"];

Expand Down Expand Up @@ -115,12 +116,12 @@ const getPolicyBlockingByRollout = (policyEvaluations?: PolicyEvaluation) => {
const policy = policyEvaluations.policies.find((p) => p.id === policyId);
if (policy == null) return null;

if (rolloutTime == null) return { policy, rolloutTime: null, passing: false };
if (rolloutTime == null) return { policy, rolloutTime: null };

const now = new Date();
if (isAfter(now, rolloutTime)) return { policy, rolloutTime, passing: true };
if (isAfter(now, rolloutTime)) return null;

return { policy, rolloutTime, passing: false };
return { policy, rolloutTime };
};

const getPoliciesBlockingByConcurrency = (
Expand Down Expand Up @@ -209,10 +210,20 @@ const BlockingReleaseTargetJobTooltip: React.FC<{
);
};

const getBlockingVersionDependencies = (
policyEvaluations?: PolicyEvaluation,
) => {
if (policyEvaluations == null) return [];
const { versionDependency } = policyEvaluations.rules;
return versionDependency.filter((v) => !v.isSatisfied);
};

export const PolicyEvaluationsCell: React.FC<{
resource: { id: string; name: string };
releaseTargetId: string;
versionId: string;
}> = ({ releaseTargetId, versionId }) => {
version: { id: string; tag: string };
}> = ({ resource, releaseTargetId, version }) => {
const versionId = version.id;
const { data: policyEvaluations, isLoading } =
api.policy.evaluate.releaseTarget.useQuery({
releaseTargetId,
Expand All @@ -236,13 +247,16 @@ export const PolicyEvaluationsCell: React.FC<{
getPoliciesBlockingByConcurrency(policyEvaluations);
const blockingReleaseTargetJob =
getBlockingReleaseTargetJob(policyEvaluations);
const blockingVersionDependencies =
getBlockingVersionDependencies(policyEvaluations);

const isBlocked =
policiesBlockingByApproval.length > 0 ||
policiesBlockingByVersionSelector.length > 0 ||
policyBlockingByRollout != null ||
policiesBlockingByConcurrency.length > 0 ||
blockingReleaseTargetJob != null;
blockingReleaseTargetJob != null ||
blockingVersionDependencies.length > 0;

if (!isBlocked)
return <div className="text-sm text-muted-foreground">No jobs</div>;
Expand Down Expand Up @@ -276,7 +290,7 @@ export const PolicyEvaluationsCell: React.FC<{
</PolicyListTooltip>
)}

{policyBlockingByRollout != null && !policyBlockingByRollout.passing && (
{policyBlockingByRollout != null && (
<PolicyListTooltip policies={[policyBlockingByRollout.policy]}>
<div className="flex items-center gap-2 rounded-md border border-blue-500 px-2 py-1 text-xs text-blue-500">
<IconCalendarTime className="h-4 w-4" />
Expand All @@ -287,6 +301,14 @@ export const PolicyEvaluationsCell: React.FC<{
</PolicyListTooltip>
)}

{blockingVersionDependencies.length > 0 && (
<VersionDependencyBadge
resource={resource}
version={version}
dependencyResults={blockingVersionDependencies}
/>
)}

{blockingReleaseTargetJob != null && (
<BlockingReleaseTargetJobTooltip jobInfo={blockingReleaseTargetJob} />
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export const ReleaseTargetRow: React.FC<{
resource: { id: string; name: string };
environment: { id: string; name: string };
deployment: { id: string; name: string };
version: { id: string };
version: { id: string; tag: string };
jobs: Array<{
id: string;
status: SCHEMA.JobStatus;
Expand Down Expand Up @@ -222,7 +222,8 @@ export const ReleaseTargetRow: React.FC<{
<TableCell className="p-0">
<PolicyEvaluationsCell
releaseTargetId={id}
versionId={version.id}
version={version}
resource={resource}
/>
</TableCell>
<TableCell />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import type * as schema from "@ctrlplane/db/schema";
import React from "react";
import Link from "next/link";
import { useParams } from "next/navigation";
import { IconSitemapFilled } from "@tabler/icons-react";

import { Badge } from "@ctrlplane/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@ctrlplane/ui/dialog";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@ctrlplane/ui/table";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@ctrlplane/ui/tooltip";

import { DeploymentVersionConditionBadge } from "~/app/[workspaceSlug]/(app)/_components/deployments/version/condition/DeploymentVersionConditionBadge";
import { ResourceIcon } from "~/app/[workspaceSlug]/(app)/_components/resources/ResourceIcon";
import { urls } from "~/app/urls";

type Dependency = schema.VersionDependency & {
resourcesForDependency: schema.Resource[];
deployment: schema.Deployment;
};

const SingleResourceCell: React.FC<{
resource: schema.Resource;
}> = ({ resource }) => {
const { workspaceSlug } = useParams<{ workspaceSlug: string }>();
const resourceUrl = urls
.workspace(workspaceSlug)
.resource(resource.id)
.deployments();

return (
<TableCell>
<Link
href={resourceUrl}
className="flex max-w-60 items-center gap-1 truncate"
>
<ResourceIcon
version={resource.version}
kind={resource.kind}
className="h-3 w-3 flex-shrink-0"
/>
{resource.name}
</Link>
</TableCell>
);
};

const ResourceCell: React.FC<{
resources: schema.Resource[];
}> = ({ resources }) => {
const { workspaceSlug } = useParams<{ workspaceSlug: string }>();

if (resources.length === 0)
return (
<TableCell>
<div className="text-xs text-muted-foreground">No resources</div>
</TableCell>
);

if (resources.length === 1)
return <SingleResourceCell resource={resources[0]!} />;

return (
<TableCell>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Badge variant="secondary" className="cursor-pointer">
{resources.length} resources
</Badge>
</TooltipTrigger>
<TooltipContent className="flex max-w-96 flex-col gap-1.5 border bg-neutral-950 p-2">
{resources.map((resource) => (
<Link
key={resource.id}
href={urls
.workspace(workspaceSlug)
.resource(resource.id)
.deployments()}
className="flex items-center gap-1 truncate"
>
<ResourceIcon
version={resource.version}
kind={resource.kind}
className="h-3 w-3 flex-shrink-0"
/>
{resource.name}
</Link>
))}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</TableCell>
);
};

const DependencyRow: React.FC<{
dependency: Dependency;
}> = ({ dependency }) => (
<TableRow>
<TableCell>{dependency.deployment.name}</TableCell>
<TableCell>
{dependency.versionSelector != null && (
<DeploymentVersionConditionBadge
condition={dependency.versionSelector}
/>
)}
{dependency.versionSelector == null && (
<div className="text-xs text-muted-foreground">No version selector</div>
)}
</TableCell>
<ResourceCell resources={dependency.resourcesForDependency} />
</TableRow>
);

export const VersionDependencyBadge: React.FC<{
resource: { id: string; name: string };
version: { tag: string };
dependencyResults: Dependency[];
}> = ({ resource, version, dependencyResults }) => (
<Dialog>
<DialogTrigger asChild>
<div className="flex items-center gap-2 rounded-md border border-neutral-500 px-2 py-1 text-xs text-neutral-500">
<IconSitemapFilled className="h-4 w-4" />
Missing dependencies
</div>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>{resource.name} is missing dependencies</DialogTitle>
<DialogDescription>
{resource.name} is missing the following dependencies specified for
version {version.tag}
</DialogDescription>
</DialogHeader>

<Table>
<TableHeader>
<TableRow>
<TableHead>Deployment</TableHead>
<TableHead>Version Selector</TableHead>
<TableHead>Resources checked</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{dependencyResults.map((dependency) => (
<DependencyRow key={dependency.id} dependency={dependency} />
))}
</TableBody>
</Table>
</DialogContent>
</Dialog>
);
Loading
Loading