diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/SearchDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/SearchDialog.tsx index 0e851c136..3777faefa 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/SearchDialog.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/SearchDialog.tsx @@ -15,6 +15,7 @@ import { CommandSeparator, } from "@ctrlplane/ui/command"; import { Dialog, DialogContent, DialogTrigger } from "@ctrlplane/ui/dialog"; +import { ColumnOperator } from "@ctrlplane/validators/conditions"; import { api } from "~/trpc/react"; @@ -40,8 +41,8 @@ export const SearchDialog: React.FC<{ children: React.ReactNode }> = ({ conditions: [ { type: "name", - operator: "like", - value: `%${search}%`, + operator: ColumnOperator.Contains, + value: search, }, ], } diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/filter/ColumnConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/filter/ColumnConditionRender.tsx index 910c14552..1993768c6 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/filter/ColumnConditionRender.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/filter/ColumnConditionRender.tsx @@ -40,7 +40,11 @@ export const ColumnConditionRender: React.FC = ({ Equals - Like + Contains + + Starts with + + Ends with Regex @@ -48,11 +52,7 @@ export const ColumnConditionRender: React.FC = ({
setValue(e.target.value)} diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-condition/JobConditionBadge.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-condition/JobConditionBadge.tsx index cd273b0f2..0ff6fb1fe 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-condition/JobConditionBadge.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-condition/JobConditionBadge.tsx @@ -24,6 +24,7 @@ import { HoverCardTrigger, } from "@ctrlplane/ui/hover-card"; import { + ColumnOperator, ComparisonOperator, DateOperator, MetadataOperator, @@ -57,6 +58,9 @@ const operatorVerbs = { [DateOperator.Before]: "before", [DateOperator.AfterOrOn]: "after or on", [DateOperator.BeforeOrOn]: "before or on", + [ColumnOperator.StartsWith]: "starts with", + [ColumnOperator.EndsWith]: "ends with", + [ColumnOperator.Contains]: "contains", }; const ConditionBadge: React.FC<{ diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-condition/JobTargetConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-condition/JobTargetConditionRender.tsx index aa9da5307..b82bb6558 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-condition/JobTargetConditionRender.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-condition/JobTargetConditionRender.tsx @@ -17,13 +17,11 @@ import { } from "@ctrlplane/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@ctrlplane/ui/popover"; import { + ColumnOperator, ComparisonOperator, FilterType, } from "@ctrlplane/validators/conditions"; -import { - ResourceFilterType, - ResourceOperator, -} from "@ctrlplane/validators/resources"; +import { ResourceFilterType } from "@ctrlplane/validators/resources"; import type { JobConditionRenderProps } from "./job-condition-props"; import { api } from "~/trpc/react"; @@ -49,8 +47,8 @@ export const JobTargetConditionRender: React.FC< const searchFilter: ResourceCondition = { type: ResourceFilterType.Name, - operator: ResourceOperator.Like, - value: `%${searchDebounced}%`, + operator: ColumnOperator.Contains, + value: searchDebounced, }; const systemQ = api.system.bySlug.useQuery( diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/release-condition/ReleaseConditionBadge.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/release-condition/ReleaseConditionBadge.tsx index 61bb8c460..3bb8d91e4 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/release-condition/ReleaseConditionBadge.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/release-condition/ReleaseConditionBadge.tsx @@ -18,7 +18,7 @@ import { HoverCardContent, HoverCardTrigger, } from "@ctrlplane/ui/hover-card"; -import { DateOperator } from "@ctrlplane/validators/conditions"; +import { ColumnOperator, DateOperator } from "@ctrlplane/validators/conditions"; import { isComparisonCondition, isCreatedAtCondition, @@ -42,6 +42,9 @@ const operatorVerbs = { [DateOperator.Before]: "before", [DateOperator.AfterOrOn]: "after or on", [DateOperator.BeforeOrOn]: "before or on", + [ColumnOperator.StartsWith]: "starts with", + [ColumnOperator.EndsWith]: "ends with", + [ColumnOperator.Contains]: "contains", }; const ConditionBadge: React.FC<{ diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/ComparisonConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/ComparisonConditionRender.tsx index 8290bccc8..dace4b76a 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/ComparisonConditionRender.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/ComparisonConditionRender.tsx @@ -288,7 +288,7 @@ export const ComparisonConditionRender: React.FC< onClick={() => addCondition({ type: ResourceFilterType.Name, - operator: ResourceOperator.Like, + operator: ColumnOperator.Equals, value: "", }) } @@ -299,7 +299,7 @@ export const ComparisonConditionRender: React.FC< onClick={() => addCondition({ type: ResourceFilterType.Identifier, - operator: ColumnOperator.Like, + operator: ColumnOperator.Equals, value: "", }) } diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/NameConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/NameConditionRender.tsx index 8da79becf..2d6107973 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/NameConditionRender.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/NameConditionRender.tsx @@ -1,35 +1,24 @@ +import type { ColumnOperatorType } from "@ctrlplane/validators/conditions"; import type { NameCondition } from "@ctrlplane/validators/resources"; -import { cn } from "@ctrlplane/ui"; -import { Input } from "@ctrlplane/ui/input"; - import type { TargetConditionRenderProps } from "./target-condition-props"; +import { ColumnConditionRender } from "../filter/ColumnConditionRender"; export const NameConditionRender: React.FC< TargetConditionRenderProps > = ({ condition, onChange, className }) => { - const setValue = (value: string) => - onChange({ ...condition, value: `%${value}%` }); + const setOperator = (operator: ColumnOperatorType) => + onChange({ ...condition, operator }); + const setValue = (value: string) => onChange({ ...condition, value }); return ( -
-
-
- Name contains -
-
- setValue(e.target.value)} - className="rounded-l-none rounded-r-md bg-transparent hover:bg-neutral-800/50" - /> -
-
-
+ ); }; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/TargetConditionBadge.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/TargetConditionBadge.tsx index becbccab0..f0ae6d8fb 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/TargetConditionBadge.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/TargetConditionBadge.tsx @@ -17,6 +17,7 @@ import { HoverCardContent, HoverCardTrigger, } from "@ctrlplane/ui/hover-card"; +import { ColumnOperator } from "@ctrlplane/validators/conditions"; import { isComparisonCondition, isIdentifierCondition, @@ -40,6 +41,9 @@ const operatorVerbs = { ), [ResourceOperator.Regex]: "matches", [ResourceOperator.Like]: "contains", + [ColumnOperator.StartsWith]: "starts with", + [ColumnOperator.EndsWith]: "ends with", + [ColumnOperator.Contains]: "contains", }; const ConditionBadge: React.FC<{ diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableResourceInput.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableResourceInput.tsx index 98566fc79..facbf80e4 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableResourceInput.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/_components/variables/VariableResourceInput.tsx @@ -16,6 +16,7 @@ import { CommandList, } from "@ctrlplane/ui/command"; import { Popover, PopoverContent, PopoverTrigger } from "@ctrlplane/ui/popover"; +import { ColumnOperator } from "@ctrlplane/validators/conditions"; import { ResourceFilterType, ResourceOperator, @@ -74,8 +75,8 @@ const useResourcesWithSearch = ( environmentFilters, { type: ResourceFilterType.Name, - operator: ResourceOperator.Like, - value: `%${search}%`, + operator: ColumnOperator.Contains, + value: search, }, ], }; diff --git a/packages/db/src/schema/job.ts b/packages/db/src/schema/job.ts index 4f186ed63..da7036fd2 100644 --- a/packages/db/src/schema/job.ts +++ b/packages/db/src/schema/job.ts @@ -11,6 +11,7 @@ import { exists, gt, gte, + ilike, isNull, like, lt, @@ -208,11 +209,15 @@ const buildCreatedAtCondition = (cond: CreatedAtCondition): SQL => { }; const buildVersionCondition = (cond: VersionCondition): SQL => { - if (cond.operator === ColumnOperator.Like) - return like(release.version, cond.value); - if (cond.operator === ColumnOperator.Regex) - return sql`${release.version} ~ ${cond.value}`; - return eq(release.version, cond.value); + if (cond.operator === ColumnOperator.Equals) + return eq(release.version, cond.value); + if (cond.operator === ColumnOperator.StartsWith) + return ilike(release.version, `${cond.value}%`); + if (cond.operator === ColumnOperator.EndsWith) + return ilike(release.version, `%${cond.value}`); + if (cond.operator === ColumnOperator.Contains) + return ilike(release.version, `%${cond.value}%`); + return sql`${release.version} ~ ${cond.value}`; }; const buildCondition = (tx: Tx, cond: JobCondition): SQL => { diff --git a/packages/db/src/schema/release.ts b/packages/db/src/schema/release.ts index a27258e7b..03ad8e80c 100644 --- a/packages/db/src/schema/release.ts +++ b/packages/db/src/schema/release.ts @@ -11,6 +11,7 @@ import { exists, gt, gte, + ilike, like, lt, lte, @@ -284,8 +285,12 @@ const buildCreatedAtCondition = (cond: CreatedAtCondition): SQL => { const buildVersionCondition = (cond: VersionCondition): SQL => { if (cond.operator === ColumnOperator.Equals) return eq(release.version, cond.value); - if (cond.operator === ColumnOperator.Like) - return like(release.version, cond.value); + if (cond.operator === ColumnOperator.StartsWith) + return ilike(release.version, `${cond.value}%`); + if (cond.operator === ColumnOperator.EndsWith) + return ilike(release.version, `%${cond.value}`); + if (cond.operator === ColumnOperator.Contains) + return ilike(release.version, `%${cond.value}%`); return sql`${release.version} ~ ${cond.value}`; }; diff --git a/packages/db/src/schema/resource.ts b/packages/db/src/schema/resource.ts index c93b74295..ecb59e0f6 100644 --- a/packages/db/src/schema/resource.ts +++ b/packages/db/src/schema/resource.ts @@ -1,10 +1,20 @@ import type { MetadataCondition } from "@ctrlplane/validators/conditions"; import type { IdentifierCondition, + NameCondition, ResourceCondition, } from "@ctrlplane/validators/resources"; import type { InferInsertModel, InferSelectModel, SQL } from "drizzle-orm"; -import { exists, like, not, notExists, or, relations, sql } from "drizzle-orm"; +import { + exists, + ilike, + like, + not, + notExists, + or, + relations, + sql, +} from "drizzle-orm"; import { boolean, json, @@ -210,20 +220,36 @@ const buildMetadataCondition = (tx: Tx, cond: MetadataCondition): SQL => { }; const buildIdentifierCondition = (tx: Tx, cond: IdentifierCondition): SQL => { - if (cond.operator === ColumnOperator.Like) - return like(resource.identifier, cond.value); if (cond.operator === ColumnOperator.Equals) return eq(resource.identifier, cond.value); + if (cond.operator === ColumnOperator.StartsWith) + return ilike(resource.identifier, `${cond.value}%`); + if (cond.operator === ColumnOperator.EndsWith) + return ilike(resource.identifier, `%${cond.value}`); + if (cond.operator === ColumnOperator.Contains) + return ilike(resource.identifier, `%${cond.value}%`); return sql`${resource.identifier} ~ ${cond.value}`; }; +const buildNameCondition = (tx: Tx, cond: NameCondition): SQL => { + if (cond.operator === ColumnOperator.Equals) + return eq(resource.name, cond.value); + if (cond.operator === ColumnOperator.StartsWith) + return ilike(resource.name, `${cond.value}%`); + if (cond.operator === ColumnOperator.EndsWith) + return ilike(resource.name, `%${cond.value}`); + if (cond.operator === ColumnOperator.Contains) + return ilike(resource.name, `%${cond.value}%`); + return sql`${resource.name} ~ ${cond.value}`; +}; + const buildCondition = (tx: Tx, cond: ResourceCondition): SQL => { if (cond.type === ResourceFilterType.Metadata) return buildMetadataCondition(tx, cond); if (cond.type === ResourceFilterType.Kind) return eq(resource.kind, cond.value); if (cond.type === ResourceFilterType.Name) - return like(resource.name, cond.value); + return buildNameCondition(tx, cond); if (cond.type === ResourceFilterType.Provider) return eq(resource.providerId, cond.value); if (cond.type === ResourceFilterType.Identifier) diff --git a/packages/validators/src/conditions/index.ts b/packages/validators/src/conditions/index.ts index d82c5cd00..d4e9e4ab9 100644 --- a/packages/validators/src/conditions/index.ts +++ b/packages/validators/src/conditions/index.ts @@ -5,14 +5,13 @@ export * from "./date-condition.js"; export enum ColumnOperator { Equals = "equals", - Like = "like", Regex = "regex", + StartsWith = "starts-with", + EndsWith = "ends-with", + Contains = "contains", } -export const columnOperator = z - .literal(ColumnOperator.Equals) - .or(z.literal(ColumnOperator.Like)) - .or(z.literal(ColumnOperator.Regex)); +export const columnOperator = z.nativeEnum(ColumnOperator); export type ColumnOperatorType = z.infer; diff --git a/packages/validators/src/resources/conditions/identifier-condition.ts b/packages/validators/src/resources/conditions/identifier-condition.ts index 7a0a92294..abd990b78 100644 --- a/packages/validators/src/resources/conditions/identifier-condition.ts +++ b/packages/validators/src/resources/conditions/identifier-condition.ts @@ -9,5 +9,3 @@ export const identifierCondition = z.object({ }); export type IdentifierCondition = z.infer; - -export type IdentifierOperator = IdentifierCondition["operator"]; diff --git a/packages/validators/src/resources/conditions/name-condition.ts b/packages/validators/src/resources/conditions/name-condition.ts index 9d8989938..0921b4e22 100644 --- a/packages/validators/src/resources/conditions/name-condition.ts +++ b/packages/validators/src/resources/conditions/name-condition.ts @@ -1,8 +1,10 @@ import { z } from "zod"; +import { columnOperator } from "../../conditions/index.js"; + export const nameCondition = z.object({ type: z.literal("name"), - operator: z.literal("like"), + operator: columnOperator, value: z.string().min(1), });