Skip to content

Commit

Permalink
fix: Filter targets by provider (#90)
Browse files Browse the repository at this point in the history
  • Loading branch information
adityachoudhari26 authored Sep 30, 2024
1 parent aff3fb1 commit e352ed4
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,17 @@ export const ComparisonConditionRender: React.FC<
>
Name
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
addCondition({
type: TargetFilterType.Provider,
operator: TargetOperator.Equals,
value: "",
})
}
>
Provider
</DropdownMenuItem>
{depth < 2 && (
<DropdownMenuItem
onClick={() =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type { ProviderCondition } from "@ctrlplane/validators/targets";
import { useState } from "react";
import { useParams } from "next/navigation";
import { IconSelector } from "@tabler/icons-react";

import { cn } from "@ctrlplane/ui";
import { Button } from "@ctrlplane/ui/button";
import {
Command,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@ctrlplane/ui/command";
import { Popover, PopoverContent, PopoverTrigger } from "@ctrlplane/ui/popover";

import type { TargetConditionRenderProps } from "./target-condition-props";
import { api } from "~/trpc/react";

export const ProviderConditionRender: React.FC<
TargetConditionRenderProps<ProviderCondition>
> = ({ condition, onChange, className }) => {
const [commandOpen, setCommandOpen] = useState(false);
const { workspaceSlug } = useParams<{ workspaceSlug: string }>();
const workspace = api.workspace.bySlug.useQuery(workspaceSlug);
const providers = api.target.provider.byWorkspaceId.useQuery(
workspace.data?.id ?? "",
{ enabled: workspace.isSuccess && workspace.data != null },
);

const setProvider = (provider: string) =>
onChange({ ...condition, value: provider });

const selectedProvider = providers.data?.find(
(provider) => provider.id === condition.value,
);

return (
<div className={cn("flex w-full items-center gap-2", className)}>
<div className="grid w-full grid-cols-12">
<div className="col-span-2 flex items-center rounded-l-md border bg-transparent px-3 text-sm text-muted-foreground">
Provider
</div>
<div className="col-span-10">
<Popover open={commandOpen} onOpenChange={setCommandOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={commandOpen}
className="w-full items-center justify-start gap-2 rounded-l-none rounded-r-md bg-transparent px-2 hover:bg-neutral-800/50"
>
<IconSelector className="h-4 w-4 text-muted-foreground" />
<span
className={cn(
selectedProvider != null && "text-muted-foreground",
)}
>
{selectedProvider != null
? selectedProvider.name
: "Select provider..."}
</span>
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-[462px] p-0">
<Command>
<CommandInput placeholder="Search provider..." />
<CommandGroup>
<CommandList>
{providers.data?.length === 0 && (
<CommandItem disabled>No providers to add</CommandItem>
)}
{providers.data?.map((provider) => (
<CommandItem
key={provider.id}
value={provider.id}
onSelect={() => {
setProvider(provider.id);
setCommandOpen(false);
}}
>
{provider.name}
</CommandItem>
))}
</CommandList>
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {
isKindCondition,
isMetadataCondition,
isNameCondition,
isProviderCondition,
} from "@ctrlplane/validators/targets";

import type { TargetConditionRenderProps } from "./target-condition-props";
import { ComparisonConditionRender } from "./ComparisonConditionRender";
import { KindConditionRender } from "./KindConditionRender";
import { MetadataConditionRender } from "./MetadataConditionRender";
import { NameConditionRender } from "./NameConditionRender";
import { ProviderConditionRender } from "./ProviderConditionRender";

/**
* The parent container should have min width of 1000px
Expand Down Expand Up @@ -65,5 +67,16 @@ export const TargetConditionRender: React.FC<
/>
);

if (isProviderCondition(condition))
return (
<ProviderConditionRender
condition={condition}
onChange={onChange}
onRemove={onRemove}
depth={depth}
className={className}
/>
);

return null;
};
2 changes: 2 additions & 0 deletions packages/db/src/schema/target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ const buildCondition = (tx: Tx, cond: TargetCondition): SQL => {

if (cond.type === "name") return like(target.name, cond.value);

if (cond.type === "provider") return eq(target.providerId, cond.value);

if (cond.conditions.length === 0) return sql`FALSE`;

const subCon = cond.conditions.map((c) => buildCondition(tx, c));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { z } from "zod";
import type { KindCondition } from "./kind-condition.js";
import type { MetadataCondition } from "./metadata-condition.js";
import type { NameCondition } from "./name-condition.js";
import type { ProviderCondition } from "./provider-condition.js";
import { kindCondition } from "./kind-condition.js";
import { metadataCondition } from "./metadata-condition.js";
import { nameCondition } from "./name-condition.js";
import { providerCondition } from "./provider-condition.js";

export const comparisonCondition: z.ZodType<ComparisonCondition> = z.lazy(() =>
z.object({
Expand All @@ -18,6 +20,7 @@ export const comparisonCondition: z.ZodType<ComparisonCondition> = z.lazy(() =>
comparisonCondition,
kindCondition,
nameCondition,
providerCondition,
]),
),
}),
Expand All @@ -28,6 +31,10 @@ export type ComparisonCondition = {
operator: "and" | "or";
not?: boolean;
conditions: Array<
ComparisonCondition | MetadataCondition | KindCondition | NameCondition
| ComparisonCondition
| MetadataCondition
| KindCondition
| NameCondition
| ProviderCondition
>;
};
1 change: 1 addition & 0 deletions packages/validators/src/targets/conditions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./name-condition.js";
export * from "./kind-condition.js";
export * from "./comparison-condition.js";
export * from "./target-condition.js";
export * from "./provider-condition.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { z } from "zod";

export const providerCondition = z.object({
type: z.literal("provider"),
operator: z.literal("equals"),
value: z.string().min(1),
});

export type ProviderCondition = z.infer<typeof providerCondition>;
13 changes: 12 additions & 1 deletion packages/validators/src/targets/conditions/target-condition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@ import type { ComparisonCondition } from "./comparison-condition.js";
import type { KindCondition } from "./kind-condition.js";
import type { MetadataCondition } from "./metadata-condition.js";
import type { NameCondition } from "./name-condition.js";
import type { ProviderCondition } from "./provider-condition.js";
import { comparisonCondition } from "./comparison-condition.js";
import { kindCondition } from "./kind-condition.js";
import { metadataCondition } from "./metadata-condition.js";
import { nameCondition } from "./name-condition.js";
import { providerCondition } from "./provider-condition.js";

export type TargetCondition =
| ComparisonCondition
| MetadataCondition
| KindCondition
| NameCondition;
| NameCondition
| ProviderCondition;

export const targetCondition = z.union([
comparisonCondition,
metadataCondition,
kindCondition,
nameCondition,
providerCondition,
]);

export enum TargetOperator {
Expand All @@ -35,6 +39,7 @@ export enum TargetFilterType {
Metadata = "metadata",
Kind = "kind",
Name = "name",
Provider = "provider",
Comparison = "comparison",
}

Expand Down Expand Up @@ -88,6 +93,11 @@ export const isNameCondition = (
condition: TargetCondition,
): condition is NameCondition => condition.type === TargetFilterType.Name;

export const isProviderCondition = (
condition: TargetCondition,
): condition is ProviderCondition =>
condition.type === TargetFilterType.Provider;

export const isValidTargetCondition = (condition: TargetCondition): boolean => {
// a default condition is valid - it means the user wants to clear the filter
// so it gets set to undefined, which matches all targets
Expand All @@ -98,6 +108,7 @@ export const isValidTargetCondition = (condition: TargetCondition): boolean => {
}
if (isKindCondition(condition)) return condition.value.length > 0;
if (isNameCondition(condition)) return condition.value.length > 0;
if (isProviderCondition(condition)) return condition.value.length > 0;
if (isMetadataCondition(condition)) {
if (condition.operator === TargetOperator.Null)
return condition.value == null && condition.key.length > 0;
Expand Down

0 comments on commit e352ed4

Please sign in to comment.