Skip to content

Commit

Permalink
add timer UI
Browse files Browse the repository at this point in the history
  • Loading branch information
adityachoudhari26 committed Dec 23, 2024
1 parent f992cba commit 67e356d
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const FlowDiagram: React.FC<{
releaseVersion: release.version,
deploymentId: release.deploymentId,
environmentId: env.id,
policyType: policy?.releaseSequencing,
policy,
label: `${env.name} - release sequencing`,
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type * as SCHEMA from "@ctrlplane/db/schema";
import type {
EnvironmentCondition,
JobCondition,
Expand All @@ -6,8 +7,11 @@ import type {
} from "@ctrlplane/validators/jobs";
import type { ReleaseCondition } from "@ctrlplane/validators/releases";
import type { NodeProps } from "reactflow";
import { useEffect, useState } from "react";
import { IconCheck, IconLoader2, IconMinus, IconX } from "@tabler/icons-react";
import { differenceInMilliseconds } from "date-fns";
import _ from "lodash";
import prettyMilliseconds from "pretty-ms";
import { Handle, Position } from "reactflow";
import colors from "tailwindcss/colors";

Expand All @@ -26,7 +30,7 @@ import { api } from "~/trpc/react";

type ReleaseSequencingNodeProps = NodeProps<{
workspaceId: string;
policyType?: "cancel" | "wait";
policy?: SCHEMA.EnvironmentPolicy;
releaseId: string;
releaseVersion: string;
deploymentId: string;
Expand All @@ -52,8 +56,8 @@ const Waiting: React.FC = () => (
);

const Loading: React.FC = () => (
<div className="animate-spin rounded-full bg-muted-foreground p-0.5 dark:text-black">
<IconLoader2 strokeWidth={3} className="h-3 w-3" />
<div className="rounded-full bg-muted-foreground p-0.5 dark:text-black">
<IconLoader2 strokeWidth={3} className="h-3 w-3 animate-spin" />
</div>
);

Expand Down Expand Up @@ -243,6 +247,72 @@ const ReleaseChannelCheck: React.FC<ReleaseSequencingNodeProps["data"]> = ({
);
};

const MinReleaseIntervalCheck: React.FC<ReleaseSequencingNodeProps["data"]> = ({
policy,
deploymentId,
environmentId,
}) => {
const [timeLeft, setTimeLeft] = useState<number | null>(null);

const { data: latestRelease, isLoading } =
api.release.latest.completed.useQuery(
{ deploymentId, environmentId },
{ enabled: policy != null },
);

useEffect(() => {
if (!latestRelease || !policy?.minimumReleaseInterval) return;

const calculateTimeLeft = () => {
const timePassed = differenceInMilliseconds(
new Date(),
latestRelease.createdAt,
);
return Math.max(0, policy.minimumReleaseInterval - timePassed);
};

setTimeLeft(calculateTimeLeft());

const interval = setInterval(() => {
const remaining = calculateTimeLeft();
setTimeLeft(remaining);

if (remaining <= 0) clearInterval(interval);
}, 1000);

return () => clearInterval(interval);
}, [latestRelease, policy?.minimumReleaseInterval]);

if (policy == null) return null;
const { minimumReleaseInterval } = policy;
if (minimumReleaseInterval === 0) return null;
if (isLoading)
return (
<div className="flex items-center gap-2">
<Loading />
</div>
);

if (latestRelease == null || timeLeft === 0) {
return (
<div className="flex items-center gap-2">
<Passing />
<span>Minimum release interval passed</span>
</div>
);
}

return (
<div className="flex items-center gap-2">
<Waiting />
<span>
Waiting {prettyMilliseconds(timeLeft ?? 0, { compact: true })} till next
release
</span>
</div>
);
};

export const ReleaseSequencingNode: React.FC<ReleaseSequencingNodeProps> = ({
data,
}) => {
Expand All @@ -255,6 +325,7 @@ export const ReleaseSequencingNode: React.FC<ReleaseSequencingNodeProps> = ({
>
<WaitingOnActiveCheck {...data} />
<ReleaseChannelCheck {...data} />
<MinReleaseIntervalCheck {...data} />
</div>
<Handle
type="target"
Expand Down
66 changes: 65 additions & 1 deletion packages/api/src/router/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import {
count,
desc,
eq,
exists,
inArray,
notExists,
takeFirst,
takeFirstOrNull,
} from "@ctrlplane/db";
Expand Down Expand Up @@ -36,7 +38,11 @@ import {
isPassingReleaseStringCheckPolicy,
} from "@ctrlplane/job-dispatch";
import { Permission } from "@ctrlplane/validators/auth";
import { jobCondition } from "@ctrlplane/validators/jobs";
import {
activeStatus,
jobCondition,
JobStatus,
} from "@ctrlplane/validators/jobs";
import { releaseCondition } from "@ctrlplane/validators/releases";

import { createTRPCRouter, protectedProcedure } from "../trpc";
Expand Down Expand Up @@ -351,5 +357,63 @@ export const releaseRouter = createTRPCRouter({
),
}),

latest: createTRPCRouter({
completed: protectedProcedure
.input(
z.object({
deploymentId: z.string().uuid(),
environmentId: z.string().uuid(),
}),
)
.meta({
authorizationCheck: ({ canUser, input }) =>
canUser.perform(Permission.ReleaseGet).on({
type: "deployment",
id: input.deploymentId,
}),
})
.query(({ input: { deploymentId, environmentId } }) =>
db
.select()
.from(release)
.innerJoin(environment, eq(environment.id, environmentId))
.where(
and(
eq(release.deploymentId, deploymentId),
exists(
db
.select()
.from(releaseJobTrigger)
.where(
and(
eq(releaseJobTrigger.releaseId, release.id),
eq(releaseJobTrigger.environmentId, environmentId),
),
)
.limit(1),
),
notExists(
db
.select()
.from(releaseJobTrigger)
.innerJoin(job, eq(releaseJobTrigger.jobId, job.id))
.where(
and(
eq(releaseJobTrigger.releaseId, release.id),
eq(releaseJobTrigger.environmentId, environmentId),
inArray(job.status, [...activeStatus, JobStatus.Pending]),
),
)
.limit(1),
),
),
)
.orderBy(desc(release.createdAt))
.limit(1)
.then(takeFirstOrNull)
.then((r) => r?.release ?? null),
),
}),

metadataKeys: releaseMetadataKeysRouter,
});

0 comments on commit 67e356d

Please sign in to comment.