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
190 changes: 78 additions & 112 deletions apps/webservice/src/app/[workspaceSlug]/_components/CreateRelease.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

import React, { useEffect, useState } from "react";
import { useParams, useRouter } from "next/navigation";
import { IconTrash } from "@tabler/icons-react";
import { valid } from "semver";
import {
IconFilterExclamation,
IconFilterFilled,
IconX,
} from "@tabler/icons-react";
import { z } from "zod";

import { Button } from "@ctrlplane/ui/button";
import { Card } from "@ctrlplane/ui/card";
import {
Dialog,
DialogContent,
Expand Down Expand Up @@ -38,21 +40,29 @@ import {
SelectValue,
} from "@ctrlplane/ui/select";
import { toast } from "@ctrlplane/ui/toast";
import {
isEmptyCondition,
releaseCondition,
ReleaseFilterType,
ReleaseOperator,
} from "@ctrlplane/validators/releases";

import { api } from "~/trpc/react";
import { ReleaseConditionDialog } from "./release-condition/ReleaseConditionDialog";

const releaseDependency = z.object({
targetMetadataGroupId: z.string().uuid().optional(),
deploymentId: z.string().uuid(),
rule: z.string().min(1).max(255),
ruleType: z.enum(["semver", "regex"]),
releaseFilter: releaseCondition,
});

const releaseForm = z.object({
systemId: z.string().uuid(),
deploymentId: z.string().uuid(),
version: z.string().min(1).max(255),
releaseDependencies: z.array(releaseDependency),
releaseDependencies: z.array(releaseDependency).refine((deps) => {
const deploymentIds = deps.map((d) => d.deploymentId);
return new Set(deploymentIds).size === deploymentIds.length;
}, "Cannot reuse a deployment in multiple release dependencies"),
});

export const CreateReleaseDialog: React.FC<{
Expand Down Expand Up @@ -87,14 +97,6 @@ export const CreateReleaseDialog: React.FC<{
workspace.data?.id ?? "",
{ enabled: workspace.data != null && workspace.data.id !== "" },
);
const targetMetadataGroups = api.target.metadataGroup.groups.useQuery(
workspace.data?.id ?? "",
{ enabled: workspace.data != null && workspace.data.id !== "" },
);
const latestRelease = api.release.list.useQuery(
{ deploymentId, limit: 1 },
{ enabled: deploymentId !== "" },
);

const [open, setOpen] = useState(false);
useEffect(() => {
Expand Down Expand Up @@ -130,10 +132,7 @@ export const CreateReleaseDialog: React.FC<{
numOfReleaseJobTriggers === 0
? `No targets to deploy release too.`
: `Dispatching ${release.releaseJobTriggers.length} job configuration${release.releaseJobTriggers.length > 1 ? "s" : ""}.`,
{
dismissible: true,
duration: 2_000,
},
{ dismissible: true, duration: 2_000 },
);

props.onClose?.();
Expand All @@ -145,15 +144,7 @@ export const CreateReleaseDialog: React.FC<{
name: "releaseDependencies",
});

useEffect(() => {
if ((latestRelease.data?.items.length ?? 0) > 0)
latestRelease.data?.items[0]!.releaseDependencies.forEach((rd) => {
append({
...rd,
targetMetadataGroupId: rd.targetMetadataGroupId ?? undefined,
});
});
}, [latestRelease.data, append, deploymentId]);
const formErrors = form.formState.errors.releaseDependencies ?? null;

return (
<Dialog open={open} onOpenChange={setOpen}>
Expand Down Expand Up @@ -243,17 +234,22 @@ export const CreateReleaseDialog: React.FC<{

<div className="flex flex-col space-y-3">
<Label>Release Dependencies</Label>
<span className="text-sm text-muted-foreground">
Dependencies must be fulfilled for a target before this Release
can be applied to that target. Read more about release
dependencies here.
</span>

{fields.map((_, index) => (
<Card className="space-y-2 p-2" key={index}>
<div className="grid grid-cols-2 gap-2">
<FormField
control={form.control}
name={`releaseDependencies.${index}.deploymentId`}
render={({ field: { value, onChange } }) => (
<FormItem className="col-span-1">
<div key={index} className="flex items-center gap-2">
<FormField
control={form.control}
name={`releaseDependencies.${index}.deploymentId`}
render={({ field: { value, onChange } }) => (
<FormItem>
<FormControl>
<Select value={value} onValueChange={onChange}>
<SelectTrigger className="h-8 text-sm">
<SelectTrigger className="w-32 text-sm">
<SelectValue
placeholder="Deployment"
key={value}
Expand All @@ -274,79 +270,43 @@ export const CreateReleaseDialog: React.FC<{
</SelectGroup>
</SelectContent>
</Select>
</FormItem>
)}
/>
</FormControl>
</FormItem>
)}
/>

<FormField
control={form.control}
name={`releaseDependencies.${index}.targetMetadataGroupId`}
render={({ field: { value, onChange } }) => (
<FormItem className="col-span-1">
<Select value={value} onValueChange={onChange}>
<SelectTrigger className="h-8 text-sm">
<SelectValue placeholder="Metadata Group" />
</SelectTrigger>
<SelectContent>
<SelectGroup>
{targetMetadataGroups.data?.map((group) => (
<SelectItem
key={group.targetMetadataGroup.id}
value={group.targetMetadataGroup.id}
>
{group.targetMetadataGroup.name}
</SelectItem>
))}
</SelectGroup>
</SelectContent>
</Select>
</FormItem>
)}
/>
</div>
<FormField
control={form.control}
name={`releaseDependencies.${index}.releaseFilter`}
render={({ field: { value, onChange } }) => (
<FormItem>
<FormControl>
<ReleaseConditionDialog
condition={value}
onChange={onChange}
>
<Button variant="ghost" size="icon">
{isEmptyCondition(value) && (
<IconFilterExclamation className="h-4 w-4" />
)}
{!isEmptyCondition(value) && (
<IconFilterFilled className="h-4 w-4" />
)}
</Button>
</ReleaseConditionDialog>
</FormControl>
</FormItem>
)}
/>

<div className="flex">
<FormField
control={form.control}
name={`releaseDependencies.${index}.ruleType`}
render={({ field: { value, onChange } }) => (
<FormItem>
<Select value={value} onValueChange={onChange}>
<SelectTrigger className="h-8 w-36 rounded-r-none text-sm">
<SelectValue placeholder="Validation" />
</SelectTrigger>
<SelectContent>
<SelectGroup className="w-36">
<SelectItem value="semver">Semver</SelectItem>
<SelectItem value="regex">Regex</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
</FormItem>
)}
/>

<FormField
control={form.control}
name={`releaseDependencies.${index}.rule`}
render={({ field }) => (
<Input
className="h-8 w-full rounded-l-none text-sm"
{...field}
/>
)}
/>

<Button
variant="ghost"
size="icon"
className="ml-2 h-8 w-8"
onClick={() => remove(index)}
>
<IconTrash />
</Button>
</div>
</Card>
<Button
variant="ghost"
size="icon"
onClick={() => remove(index)}
>
<IconX className="h-4 w-4" />
</Button>
</div>
))}

<Button
Expand All @@ -356,18 +316,24 @@ export const CreateReleaseDialog: React.FC<{
onClick={() =>
append({
deploymentId: "",
rule: "",
ruleType:
valid(latestRelease.data?.items[0]?.version) != null
? "semver"
: "regex",
releaseFilter: {
type: ReleaseFilterType.Comparison,
operator: ReleaseOperator.And,
conditions: [],
},
})
}
>
Add
</Button>
</div>

{formErrors?.root?.message && (
<div className="text-sm text-red-500">
{formErrors.root.message}
</div>
)}

<DialogFooter>
<Button type="submit">Create</Button>
</DialogFooter>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { api } from "~/trpc/react";
import { JobAgent } from "./JobAgent";
import { JobMetadata } from "./JobMetadata";
import { JobPropertiesTable } from "./JobProperties";
import { DependenciesDiagram } from "./RelationshipsDiagramDependencies";
import { useJobDrawer } from "./useJobDrawer";

export const JobDrawer: React.FC = () => {
Expand All @@ -43,7 +44,7 @@ export const JobDrawer: React.FC = () => {
<Drawer open={isOpen} onOpenChange={setIsOpen}>
<DrawerContent
showBar={false}
className="left-auto right-0 top-0 mt-0 h-screen w-1/3 overflow-auto rounded-none focus-visible:outline-none"
className="scrollbar-thin scrollbar-thumb-neutral-800 scrollbar-track-neutral-900 left-auto right-0 top-0 mt-0 h-screen w-2/3 overflow-auto rounded-none focus-visible:outline-none"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Consider adding responsive width classes for better mobile experience

The drawer width has been increased to w-2/3 without responsive breakpoints. Based on the codebase analysis:

  1. The base Drawer component from UI package uses a flexible right-aligned layout
  2. No other drawers in the codebase enforce a fixed w-2/3 width
  3. No responsive width classes are currently applied

Consider adding responsive classes like w-full sm:w-2/3 to ensure better usability on mobile devices while maintaining the desired width on larger screens.

🔗 Analysis chain

Verify the drawer width change impact on smaller screens.

The drawer width has been increased from 1/3 to 2/3 of the screen. While this provides more space for the dependency diagram, it might affect usability on smaller screens.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for other drawer implementations to ensure consistent width patterns
rg -g '*.tsx' -g '*.jsx' 'w-2/3.*DrawerContent' 

Length of output: 49


Script:

#!/bin/bash
# Let's search for drawer width patterns more broadly
rg -g '*.tsx' -g '*.jsx' 'w-(\d/\d|full|screen).*drawer'

# Also search for any DrawerContent components
ast-grep --pattern 'const DrawerContent = $_'

Length of output: 1561


Script:

#!/bin/bash
# Let's check how this JobDrawer component is using the base Drawer component
ast-grep --pattern 'import { Drawer$_ } from "@coderabbit/ui"'

# And check for any responsive styling patterns in the file
rg -g "JobDrawer.tsx" '(sm:|md:|lg:)w-'

Length of output: 104

>
{jobQ.isLoading && (
<div className="flex h-full w-full items-center justify-center">
Expand Down Expand Up @@ -96,10 +97,24 @@ export const JobDrawer: React.FC = () => {
)}
</DrawerTitle>
{job != null && (
<div className="flex w-full flex-col gap-6 p-6">
<JobPropertiesTable job={job} />
<div className="flex h-full flex-col gap-6 p-6">
<div className="grid grid-cols-2 gap-6">
<div className="col-span-1">
<JobPropertiesTable job={job} />
</div>
<div className="col-span-1">
<JobAgent job={job} />
</div>
</div>

<JobMetadata job={job} />
<JobAgent job={job} />

<DependenciesDiagram
targetId={job.target.id}
relationships={job.relationships}
targets={job.relatedTargets}
releaseDependencies={job.releaseDependencies}
/>
</div>
)}
</>
Expand Down
Loading
Loading