Skip to content

Commit

Permalink
add release navside bar
Browse files Browse the repository at this point in the history
  • Loading branch information
jsbroks committed Oct 4, 2024
1 parent 05c8115 commit b7319e6
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ loader.init().then((monaco) => {
});
});

export const TargetConfigEditor: React.FC<{
export const ConfigEditor: React.FC<{
value: string;
onChange?: (v: string) => void;
readOnly?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { Input } from "@ctrlplane/ui/input";
import { Label } from "@ctrlplane/ui/label";

import { api } from "~/trpc/react";
import { TargetConfigEditor } from "./TargetConfigEditor";
import { ConfigEditor } from "./ConfigEditor";

const createTargetSchema = z.object({
name: z.string(),
Expand Down Expand Up @@ -183,7 +183,7 @@ export const CreateTargetDialog: React.FC<{
<FormItem>
<FormLabel>Config</FormLabel>
<FormControl>
<TargetConfigEditor value={value} onChange={onChange} />
<ConfigEditor value={value} onChange={onChange} />
</FormControl>
<FormMessage />
</FormItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { Input } from "@ctrlplane/ui/input";
import { Label } from "@ctrlplane/ui/label";

import { api } from "~/trpc/react";
import { TargetConfigEditor } from "./TargetConfigEditor";
import { ConfigEditor } from "./ConfigEditor";

type TargetWithMetadata = Target & {
metadata: Record<string, string>;
Expand Down Expand Up @@ -207,7 +207,7 @@ export const EditTargetDialog: React.FC<{
<FormItem>
<FormLabel>Config</FormLabel>
<FormControl>
<TargetConfigEditor value={value} onChange={onChange} />
<ConfigEditor value={value} onChange={onChange} />
</FormControl>
<FormMessage />
</FormItem>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
"use client";

import type { Release, ReleaseDependency } from "@ctrlplane/db/schema";
import { useRouter, useSearchParams } from "next/navigation";
import { IconSparkles } from "@tabler/icons-react";
import { format } from "date-fns";
import yaml from "js-yaml";

import { Drawer, DrawerContent, DrawerTitle } from "@ctrlplane/ui/drawer";
import { Input } from "@ctrlplane/ui/input";
import { ReservedMetadataKey } from "@ctrlplane/validators/targets";

import { api } from "~/trpc/react";
import { useMatchSorterWithSearch } from "~/utils/useMatchSorter";
import { ConfigEditor } from "../ConfigEditor";

const param = "release_id";
export const useReleaseDrawer = () => {
const router = useRouter();
const params = useSearchParams();
const releaseId = params.get(param);

const setReleaseId = (id: string | null) => {
const url = new URL(window.location.href);
if (id === null) {
url.searchParams.delete(param);
} else {
url.searchParams.set(param, id);
}
router.replace(url.toString());
};

const removeReleaseId = () => setReleaseId(null);

return { releaseId, setReleaseId, removeReleaseId };
};

export const ReleaseDrawer: React.FC = () => {
const { releaseId, removeReleaseId } = useReleaseDrawer();
const isOpen = releaseId != null && releaseId != "";
const setIsOpen = removeReleaseId;
const releaseQ = api.release.byId.useQuery(releaseId ?? "", {
enabled: isOpen,
refetchInterval: 10_000,
});
const release = releaseQ.data;

return (
<Drawer open={isOpen} onOpenChange={setIsOpen}>
<DrawerContent
showBar={false}
className="left-auto right-0 top-0 mt-0 h-screen w-2/3 overflow-auto rounded-none focus-visible:outline-none"
>
<div className="border-b p-6">
<div className="flex items-center">
<DrawerTitle className="flex-grow">{release?.name}</DrawerTitle>
</div>
</div>
<div className="flex w-full gap-6 p-6">
{release && <OverviewContent release={release} />}
</div>
</DrawerContent>
</Drawer>
);
};

const ReleaseConfigInfo: React.FC<{ config: Record<string, any> }> = ({
config,
}) => {
yaml.dump(config);
return <ConfigEditor value={yaml.dump(config)} readOnly />;
};

const OverviewContent: React.FC<{
release: Release & {
metadata: Record<string, string>;
dependencies: ReleaseDependency[];
};
}> = ({ release }) => {
const { metadata } = release;
const links =
metadata[ReservedMetadataKey.Links] != null
? (JSON.parse(metadata[ReservedMetadataKey.Links]) as Record<
string,
string
>)
: null;

return (
<div className="space-y-4">
<div className="space-y-2">
<div className="text-sm">Properties</div>
<div className="grid grid-cols-2 gap-4">
<div>
<table
width="100%"
className="text-xs"
style={{ tableLayout: "fixed" }}
>
<tbody>
<tr>
<td className="p-1 pr-2 text-muted-foreground">Name</td>
<td>{release.name}</td>
</tr>
<tr>
<td className="p-1 pr-2 text-muted-foreground">Version</td>
<td>{release.version}</td>
</tr>

<tr>
<td className="p-1 pr-2 text-muted-foreground">Created At</td>
<td>{format(release.createdAt, "MM/dd/yyyy mm:hh:ss")}</td>
</tr>
<tr>
<td className="p-1 pr-2 align-top text-muted-foreground">
Links
</td>
<td>
{links == null ? (
<span className="cursor-help italic text-gray-500">
Not set
</span>
) : (
<>
{Object.entries(links).map(([name, url]) => (
<a
key={name}
referrerPolicy="no-referrer"
href={url}
className="inline-block w-full overflow-hidden text-ellipsis text-nowrap text-blue-300 hover:text-blue-400"
>
{name}
</a>
))}
</>
)}
</td>
</tr>
</tbody>
</table>
</div>
<div>
<table
width="100%"
className="text-xs"
style={{ tableLayout: "fixed" }}
>
<tbody>
<tr>
<td className="w-[110px] p-1 pr-2 text-muted-foreground">
External ID
</td>
<td>
<div className="overflow-hidden text-ellipsis whitespace-nowrap"></div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>

<div>
<div className="mb-2 text-sm">Config</div>
<div className="text-xs">
<ReleaseConfigInfo config={release.config} />
</div>
</div>

<div>
<div className="mb-2 text-sm">
Metadata ({Object.keys(metadata).length})
</div>
<div className="text-xs">
<ReleaseMetadataInfo metadata={metadata} />
</div>
</div>
</div>
);
};

const ReleaseMetadataInfo: React.FC<{ metadata: Record<string, string> }> = (
props,
) => {
const metadata = Object.entries(props.metadata).sort(([keyA], [keyB]) =>
keyA.localeCompare(keyB),
);
const { search, setSearch, result } = useMatchSorterWithSearch(metadata, {
keys: ["0", "1"],
});
return (
<div>
<div className="text-xs">
<div>
<Input
className="w-full rounded-b-none text-xs"
placeholder="Search ..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
<div className="scrollbar-thin scrollbar-thumb-neutral-800 scrollbar-track-neutral-900 max-h-[250px] overflow-auto rounded-b-lg border-x border-b p-1.5">
{result.map(([key, value]) => (
<div className="text-nowrap font-mono" key={key}>
<span>
{Object.values(ReservedMetadataKey).includes(
key as ReservedMetadataKey,
) && (
<IconSparkles className="inline-block h-3 w-3 text-yellow-300" />
)}{" "}
</span>
<span className="text-red-400">{key}:</span>
<span className="text-green-300"> {value}</span>
</div>
))}
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ import { ReservedMetadataKey } from "@ctrlplane/validators/targets";

import { api } from "~/trpc/react";
import { useMatchSorterWithSearch } from "~/utils/useMatchSorter";
import { TargetConfigEditor } from "../TargetConfigEditor";
import { ConfigEditor } from "../ConfigEditor";

const TargetConfigInfo: React.FC<{ config: Record<string, any> }> = ({
config,
}) => {
yaml.dump(config);
return <TargetConfigEditor value={yaml.dump(config)} readOnly />;
return <ConfigEditor value={yaml.dump(config)} readOnly />;
};

const TargetMetadataInfo: React.FC<{ metadata: Record<string, string> }> = (
Expand Down
2 changes: 2 additions & 0 deletions apps/webservice/src/app/[workspaceSlug]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { notFound, redirect } from "next/navigation";
import { auth } from "@ctrlplane/auth";

import { api } from "~/trpc/server";
import { ReleaseDrawer } from "./_components/release-drawer/ReleaseDrawer";
import { TargetDrawer } from "./_components/target-drawer/TargetDrawer";
import { SidebarPanels } from "./SidebarPanels";

Expand Down Expand Up @@ -31,6 +32,7 @@ export default async function WorkspaceLayout({
</SidebarPanels>
</div>
<TargetDrawer />
<ReleaseDrawer />
</>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Badge } from "@ctrlplane/ui/badge";
import { Button } from "@ctrlplane/ui/button";

import { CreateReleaseDialog } from "~/app/[workspaceSlug]/_components/CreateRelease";
import { useReleaseDrawer } from "~/app/[workspaceSlug]/_components/release-drawer/ReleaseDrawer";
import { api } from "~/trpc/react";
import { DeployButton } from "./DeployButton";
import { Release } from "./TableCells";
Expand Down Expand Up @@ -83,6 +84,7 @@ export const ReleaseTable: React.FC<{
targets: { id: string }[];
}[];
}> = ({ deployment, environments }) => {
const { setReleaseId } = useReleaseDrawer();
const { workspaceSlug, systemSlug } = useParams<{
workspaceSlug: string;
systemSlug: string;
Expand Down Expand Up @@ -168,6 +170,7 @@ export const ReleaseTable: React.FC<{
return (
<tr key={r.id} className="bg-neutral-800/10">
<td
onClick={() => setReleaseId(r.id)}
className={cn(
"sticky left-0 z-10 min-w-[250px] backdrop-blur-lg",
"items-center border-b border-l px-4 text-lg",
Expand Down
16 changes: 15 additions & 1 deletion packages/api/src/router/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
release,
releaseDependency,
releaseJobTrigger,
releaseMetadata,
target,
} from "@ctrlplane/db/schema";
import {
Expand Down Expand Up @@ -110,7 +111,20 @@ export const releaseRouter = createTRPCRouter({
}))
.value()
.at(0),
),
)
.then(async (data) => {
if (data == null) return null;
return {
...data,
metadata: Object.fromEntries(
await ctx.db
.select()
.from(releaseMetadata)
.where(eq(releaseMetadata.releaseId, data.id))
.then((r) => r.map((k) => [k.key, k.value])),
),
};
}),
),

deploy: createTRPCRouter({
Expand Down
13 changes: 11 additions & 2 deletions packages/db/src/schema/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export const releaseDependency = pgTable(
}),
);

export type ReleaseDependency = InferSelectModel<typeof releaseDependency>;

const createReleaseDependency = createInsertSchema(releaseDependency).omit({
id: true,
});
Expand All @@ -55,7 +57,10 @@ export const release = pgTable(
id: uuid("id").primaryKey().defaultRandom(),
name: text("name").notNull(),
version: text("version").notNull(),
config: jsonb("config").notNull().default("{}"),
config: jsonb("config")
.notNull()
.default("{}")
.$type<Record<string, any>>(),
deploymentId: uuid("deployment_id")
.notNull()
.references(() => deployment.id, { onDelete: "cascade" }),
Expand All @@ -66,7 +71,11 @@ export const release = pgTable(

export type Release = InferSelectModel<typeof release>;

export const createRelease = createInsertSchema(release)
export const createRelease = createInsertSchema(release, {
version: z.string().min(1),
name: z.string().min(1),
config: z.record(z.any()),
})
.omit({ id: true })
.extend({
releaseDependencies: z
Expand Down

0 comments on commit b7319e6

Please sign in to comment.