-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Deployment level resource filtering (#273)
- Loading branch information
1 parent
e0409a2
commit c243f95
Showing
15 changed files
with
4,898 additions
and
105 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
.../webservice/src/app/[workspaceSlug]/(app)/_components/resource-condition/ResourceList.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import type { RouterOutputs } from "@ctrlplane/api"; | ||
import type { ResourceCondition } from "@ctrlplane/validators/resources"; | ||
import Link from "next/link"; | ||
import { useParams } from "next/navigation"; | ||
import { IconExternalLink } from "@tabler/icons-react"; | ||
import * as LZString from "lz-string"; | ||
|
||
import { Button } from "@ctrlplane/ui/button"; | ||
import { Label } from "@ctrlplane/ui/label"; | ||
|
||
import { ResourceIcon } from "../ResourceIcon"; | ||
|
||
type Resource = | ||
RouterOutputs["resource"]["byWorkspaceId"]["list"]["items"][number]; | ||
|
||
type ResourceListProps = { | ||
resources: Resource[]; | ||
count: number; | ||
filter: ResourceCondition; | ||
}; | ||
|
||
export const ResourceList: React.FC<ResourceListProps> = ({ | ||
resources, | ||
count, | ||
filter, | ||
}) => { | ||
const { workspaceSlug } = useParams<{ workspaceSlug: string }>(); | ||
|
||
return ( | ||
<div className="space-y-4"> | ||
<Label>Resources ({count})</Label> | ||
<div className="space-y-2"> | ||
{resources.map((resource) => ( | ||
<div className="flex items-center gap-2" key={resource.id}> | ||
<ResourceIcon version={resource.version} kind={resource.kind} /> | ||
<div className="flex flex-col"> | ||
<span className="overflow-hidden text-nowrap text-sm"> | ||
{resource.name} | ||
</span> | ||
<span className="text-xs text-muted-foreground"> | ||
{resource.version} | ||
</span> | ||
</div> | ||
</div> | ||
))} | ||
</div> | ||
<Button variant="outline" size="sm"> | ||
<Link | ||
href={`/${workspaceSlug}/resources?${new URLSearchParams({ | ||
filter: LZString.compressToEncodedURIComponent( | ||
JSON.stringify(filter), | ||
), | ||
})}`} | ||
className="flex items-center gap-1" | ||
target="_blank" | ||
rel="noopener noreferrer" | ||
> | ||
<IconExternalLink className="h-4 w-4" /> | ||
View Resources | ||
</Link> | ||
</Button> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
...ug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/DeploymentResourcesDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import type { ResourceCondition } from "@ctrlplane/validators/resources"; | ||
import { useState } from "react"; | ||
import { IconFilter } from "@tabler/icons-react"; | ||
import { isPresent } from "ts-is-present"; | ||
|
||
import { Button } from "@ctrlplane/ui/button"; | ||
import { | ||
Dialog, | ||
DialogContent, | ||
DialogDescription, | ||
DialogHeader, | ||
DialogTitle, | ||
DialogTrigger, | ||
} from "@ctrlplane/ui/dialog"; | ||
import { | ||
Select, | ||
SelectContent, | ||
SelectItem, | ||
SelectTrigger, | ||
SelectValue, | ||
} from "@ctrlplane/ui/select"; | ||
import { | ||
ComparisonOperator, | ||
FilterType, | ||
} from "@ctrlplane/validators/conditions"; | ||
import { isValidResourceCondition } from "@ctrlplane/validators/resources"; | ||
|
||
import { ResourceConditionRender } from "~/app/[workspaceSlug]/(app)/_components/resource-condition/ResourceConditionRender"; | ||
import { ResourceList } from "~/app/[workspaceSlug]/(app)/_components/resource-condition/ResourceList"; | ||
import { api } from "~/trpc/react"; | ||
|
||
type Environment = { | ||
id: string; | ||
name: string; | ||
resourceFilter: ResourceCondition; | ||
}; | ||
type DeploymentResourcesDialogProps = { | ||
environments: Environment[]; | ||
resourceFilter: ResourceCondition; | ||
workspaceId: string; | ||
}; | ||
|
||
export const DeploymentResourcesDialog: React.FC< | ||
DeploymentResourcesDialogProps | ||
> = ({ environments, resourceFilter, workspaceId }) => { | ||
const [selectedEnvironment, setSelectedEnvironment] = | ||
useState<Environment | null>(environments[0] ?? null); | ||
|
||
const filter: ResourceCondition = { | ||
type: FilterType.Comparison, | ||
operator: ComparisonOperator.And, | ||
conditions: [selectedEnvironment?.resourceFilter, resourceFilter].filter( | ||
isPresent, | ||
), | ||
}; | ||
const isFilterValid = isValidResourceCondition(filter); | ||
|
||
const { data, isLoading } = api.resource.byWorkspaceId.list.useQuery( | ||
{ workspaceId, filter, limit: 5 }, | ||
{ enabled: selectedEnvironment != null && isFilterValid }, | ||
); | ||
|
||
const resources = data?.items ?? []; | ||
const count = data?.total ?? 0; | ||
|
||
if (environments.length === 0) return null; | ||
return ( | ||
<Dialog> | ||
<DialogTrigger asChild> | ||
<Button | ||
variant="outline" | ||
className="flex items-center gap-2" | ||
type="button" | ||
disabled={!isValidResourceCondition(resourceFilter)} | ||
> | ||
<IconFilter className="h-4 w-4" /> View Resources | ||
</Button> | ||
</DialogTrigger> | ||
<DialogContent className="min-w-[1000px] space-y-6"> | ||
<DialogHeader> | ||
<DialogTitle>View Resources</DialogTitle> | ||
<DialogDescription> | ||
Select an environment to view the resources based on the combined | ||
environment and deployment filter. | ||
</DialogDescription> | ||
</DialogHeader> | ||
|
||
<Select | ||
value={selectedEnvironment?.id} | ||
onValueChange={(value) => { | ||
const environment = environments.find((e) => e.id === value); | ||
setSelectedEnvironment(environment ?? null); | ||
}} | ||
> | ||
<SelectTrigger> | ||
<SelectValue placeholder="Select an environment" /> | ||
</SelectTrigger> | ||
<SelectContent> | ||
{environments.map((environment) => ( | ||
<SelectItem key={environment.id} value={environment.id}> | ||
{environment.name} | ||
</SelectItem> | ||
))} | ||
</SelectContent> | ||
</Select> | ||
|
||
{selectedEnvironment != null && ( | ||
<> | ||
<ResourceConditionRender condition={filter} onChange={() => {}} /> | ||
{!isLoading && ( | ||
<ResourceList | ||
resources={resources} | ||
count={count} | ||
filter={filter} | ||
/> | ||
)} | ||
</> | ||
)} | ||
</DialogContent> | ||
</Dialog> | ||
); | ||
}; |
Oops, something went wrong.