diff --git a/apps/event-worker/src/target-scan/gke.ts b/apps/event-worker/src/target-scan/gke.ts index b51ca42c..ff7c717b 100644 --- a/apps/event-worker/src/target-scan/gke.ts +++ b/apps/event-worker/src/target-scan/gke.ts @@ -8,7 +8,7 @@ import { CoreV1Api } from "@kubernetes/client-node"; import _ from "lodash"; import { logger } from "@ctrlplane/logger"; -import { ReservedMetadataKey } from "@ctrlplane/validators/targets"; +import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; import { clusterToTarget, diff --git a/apps/event-worker/src/target-scan/google.ts b/apps/event-worker/src/target-scan/google.ts index 96c4b6f3..79a4ffbf 100644 --- a/apps/event-worker/src/target-scan/google.ts +++ b/apps/event-worker/src/target-scan/google.ts @@ -7,7 +7,7 @@ import { KubeConfig } from "@kubernetes/client-node"; import { GoogleAuth, Impersonated } from "google-auth-library"; import { SemVer } from "semver"; -import { ReservedMetadataKey } from "@ctrlplane/validators/targets"; +import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; import { omitNullUndefined } from "../utils.js"; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/filter/ChoiceConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/filter/ChoiceConditionRender.tsx new file mode 100644 index 00000000..f82f5934 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/_components/filter/ChoiceConditionRender.tsx @@ -0,0 +1,85 @@ +import { useState } from "react"; +import { IconSelector } from "@tabler/icons-react"; +import { capitalCase } from "change-case"; + +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"; + +type ChoiceConditionRenderProps = { + onSelect: (value: string) => void; + type: string; + selected: string | null; + options: { key: string; value: string; display: string }[]; + className?: string; +}; + +export const ChoiceConditionRender: React.FC = ({ + onSelect, + type, + selected, + options, + className, +}) => { + const [open, setOpen] = useState(false); + + return ( +
+
+
+ {capitalCase(type)} +
+
+ + + + + + + + + + {options.length === 0 && ( + No options to add + )} + {options.map((option) => ( + { + onSelect(option.key); + setOpen(false); + }} + > + {option.display} + + ))} + + + + + +
+
+
+ ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/filter/DateConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/filter/DateConditionRender.tsx new file mode 100644 index 00000000..c55a35f5 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/_components/filter/DateConditionRender.tsx @@ -0,0 +1,92 @@ +import type { DateValue } from "@internationalized/date"; +import { ZonedDateTime } from "@internationalized/date"; +import ms from "ms"; + +import { cn } from "@ctrlplane/ui"; +import { DateTimePicker } from "@ctrlplane/ui/date-time-picker/date-time-picker"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@ctrlplane/ui/select"; +import { DateOperator } from "@ctrlplane/validators/conditions"; + +const toZonedDateTime = (date: Date): ZonedDateTime => { + const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const offset = -date.getTimezoneOffset() * ms("1m"); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hour = date.getHours(); + const minute = date.getMinutes(); + const second = date.getSeconds(); + const millisecond = date.getMilliseconds(); + + return new ZonedDateTime( + year, + month, + day, + timeZone, + offset, + hour, + minute, + second, + millisecond, + ); +}; + +type Operator = "before" | "after" | "before-or-on" | "after-or-on"; + +type DateConditionRenderProps = { + setDate: (date: DateValue) => void; + setOperator: (operator: DateOperator) => void; + value: string; + operator: Operator; + type: string; + className?: string; +}; + +export const DateConditionRender: React.FC = ({ + setDate, + setOperator, + value, + operator, + type, + className, +}) => ( +
+
+
+ {type} +
+
+ +
+
+ +
+
+
+); diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/MetadataConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/filter/MetadataConditionRender.tsx similarity index 67% rename from apps/webservice/src/app/[workspaceSlug]/_components/target-condition/MetadataConditionRender.tsx rename to apps/webservice/src/app/[workspaceSlug]/_components/filter/MetadataConditionRender.tsx index 203a466c..b4a7b074 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/MetadataConditionRender.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/filter/MetadataConditionRender.tsx @@ -1,6 +1,8 @@ -import type { MetadataCondition } from "@ctrlplane/validators/targets"; +import type { + MetadataCondition, + MetadataOperatorType, +} from "@ctrlplane/validators/conditions"; import { useState } from "react"; -import { useParams } from "next/navigation"; import { cn } from "@ctrlplane/ui"; import { Button } from "@ctrlplane/ui/button"; @@ -13,44 +15,33 @@ import { SelectTrigger, SelectValue, } from "@ctrlplane/ui/select"; -import { TargetOperator } from "@ctrlplane/validators/targets"; +import { MetadataOperator } from "@ctrlplane/validators/conditions"; -import type { TargetConditionRenderProps } from "./target-condition-props"; -import { api } from "~/trpc/react"; import { useMatchSorter } from "~/utils/useMatchSorter"; +type MetadataConditionRenderProps = { + condition: MetadataCondition; + onChange: (condition: MetadataCondition) => void; + metadataKeys: string[]; + className?: string; +}; export const MetadataConditionRender: React.FC< - TargetConditionRenderProps -> = ({ condition, onChange, className }) => { - const { workspaceSlug } = useParams<{ workspaceSlug: string }>(); - const workspace = api.workspace.bySlug.useQuery(workspaceSlug); - + MetadataConditionRenderProps +> = ({ condition, onChange, metadataKeys, className }) => { const setKey = (key: string) => onChange({ ...condition, key }); const setValue = (value: string) => - condition.operator !== TargetOperator.Null && + condition.operator !== MetadataOperator.Null && onChange({ ...condition, value }); - const setOperator = ( - operator: - | TargetOperator.Equals - | TargetOperator.Like - | TargetOperator.Regex - | TargetOperator.Null, - ) => - operator === TargetOperator.Null + const setOperator = (operator: MetadataOperatorType) => + operator === MetadataOperator.Null ? onChange({ ...condition, operator, value: undefined }) : onChange({ ...condition, operator, value: condition.value ?? "" }); const [open, setOpen] = useState(false); - const metadataKeys = api.target.metadataKeys.useQuery( - workspace.data?.id ?? "", - { enabled: workspace.isSuccess && workspace.data != null }, - ); - const filteredMetadataKeys = useMatchSorter( - metadataKeys.data ?? [], - condition.key, - ); + + const filteredMetadataKeys = useMatchSorter(metadataKeys, condition.key); return (
@@ -92,10 +83,10 @@ export const MetadataConditionRender: React.FC< value={condition.operator} onValueChange={( v: - | TargetOperator.Equals - | TargetOperator.Like - | TargetOperator.Regex - | TargetOperator.Null, + | MetadataOperator.Equals + | MetadataOperator.Like + | MetadataOperator.Regex + | MetadataOperator.Null, ) => setOperator(v)} > @@ -105,21 +96,21 @@ export const MetadataConditionRender: React.FC< /> - Equals - Regex - Like - Is Null + Equals + Regex + Like + Is Null
- {condition.operator !== TargetOperator.Null ? ( + {condition.operator !== MetadataOperator.Null ? (
{ - const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const offset = -date.getTimezoneOffset() * ms("1m"); - const year = date.getFullYear(); - const month = date.getMonth() + 1; - const day = date.getDate(); - const hour = date.getHours(); - const minute = date.getMinutes(); - const second = date.getSeconds(); - const millisecond = date.getMilliseconds(); - - return new ZonedDateTime( - year, - month, - day, - timeZone, - offset, - hour, - minute, - second, - millisecond, - ); -}; +import { DateConditionRender } from "../filter/DateConditionRender"; export const CreatedAtConditionRender: React.FC< ReleaseConditionRenderProps @@ -53,47 +20,20 @@ export const CreatedAtConditionRender: React.FC< const setOperator = ( operator: - | ReleaseOperator.Before - | ReleaseOperator.After - | ReleaseOperator.BeforeOrOn - | ReleaseOperator.AfterOrOn, + | DateOperator.Before + | DateOperator.After + | DateOperator.BeforeOrOn + | DateOperator.AfterOrOn, ) => onChange({ ...condition, operator }); return ( -
-
-
- Created at -
-
- -
-
- -
-
-
+ ); }; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/MetadataConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/MetadataConditionRender.tsx deleted file mode 100644 index db44b876..00000000 --- a/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/MetadataConditionRender.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import type { MetadataCondition } from "@ctrlplane/validators/releases"; -import { useState } from "react"; -import { useParams } from "next/navigation"; -import { IconLoader2 } from "@tabler/icons-react"; - -import { cn } from "@ctrlplane/ui"; -import { Button } from "@ctrlplane/ui/button"; -import { Input } from "@ctrlplane/ui/input"; -import { Popover, PopoverContent, PopoverTrigger } from "@ctrlplane/ui/popover"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@ctrlplane/ui/select"; -import { ReleaseOperator } from "@ctrlplane/validators/releases"; - -import type { ReleaseConditionRenderProps } from "./release-condition-props"; -import { api } from "~/trpc/react"; -import { useMatchSorter } from "~/utils/useMatchSorter"; - -export const MetadataConditionRender: React.FC< - ReleaseConditionRenderProps -> = ({ condition, onChange, className }) => { - const { workspaceSlug, systemSlug } = useParams<{ - workspaceSlug: string; - systemSlug?: string; - }>(); - - const workspaceQ = api.workspace.bySlug.useQuery(workspaceSlug); - const workspace = workspaceQ.data; - const systemQ = api.system.bySlug.useQuery( - { workspaceSlug, systemSlug: systemSlug ?? "" }, - { enabled: systemSlug != null }, - ); - const system = systemQ.data; - - const workspaceMetadataKeys = api.release.metadataKeys.byWorkspace.useQuery( - workspace?.id ?? "", - { enabled: workspace != null && system == null }, - ); - const systemMetadataKeys = api.release.metadataKeys.bySystem.useQuery( - system?.id ?? "", - { enabled: system != null }, - ); - - const metadataKeys = - systemMetadataKeys.data ?? workspaceMetadataKeys.data ?? []; - - const setKey = (key: string) => onChange({ ...condition, key }); - - const setValue = (value: string) => - condition.operator !== ReleaseOperator.Null && - onChange({ ...condition, value }); - - const setOperator = ( - operator: - | ReleaseOperator.Equals - | ReleaseOperator.Like - | ReleaseOperator.Regex - | ReleaseOperator.Null, - ) => - operator === ReleaseOperator.Null - ? onChange({ ...condition, operator, value: undefined }) - : onChange({ ...condition, operator, value: condition.value ?? "" }); - - const [open, setOpen] = useState(false); - const filteredMetadataKeys = useMatchSorter(metadataKeys, condition.key); - - const loadingMetadataKeys = - workspaceQ.isLoading || - systemQ.isLoading || - workspaceMetadataKeys.isLoading || - systemMetadataKeys.isLoading; - - return ( -
-
-
- - - setKey(e.target.value)} - className="w-full cursor-pointer rounded-l-sm rounded-r-none" - /> - - e.preventDefault()} - > - {!loadingMetadataKeys && - filteredMetadataKeys.map((k) => ( - - ))} - {loadingMetadataKeys && ( -
- Loading - keys... -
- )} -
-
-
-
- -
- - {condition.operator !== ReleaseOperator.Null ? ( -
- setValue(e.target.value)} - className="rounded-l-none rounded-r-sm hover:bg-neutral-800/50" - /> -
- ) : ( -
- )} -
-
- ); -}; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/ReleaseConditionBadge.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/ReleaseConditionBadge.tsx index 0e04ecda..5f3481e3 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/ReleaseConditionBadge.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/ReleaseConditionBadge.tsx @@ -1,7 +1,9 @@ import type { - ComparisonCondition, CreatedAtCondition, MetadataCondition, +} from "@ctrlplane/validators/conditions"; +import type { + ComparisonCondition, ReleaseCondition, VersionCondition, } from "@ctrlplane/validators/releases"; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/ReleaseConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/ReleaseConditionRender.tsx index 4ef148d0..b273d357 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/ReleaseConditionRender.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/release-condition/ReleaseConditionRender.tsx @@ -11,7 +11,7 @@ import { import type { ReleaseConditionRenderProps } from "./release-condition-props"; import { ComparisonConditionRender } from "./ComparisonConditionRender"; import { CreatedAtConditionRender } from "./CreatedAtConditionRender"; -import { MetadataConditionRender } from "./MetadataConditionRender"; +import { ReleaseMetadataConditionRender } from "./ReleaseMetadataConditionRender"; import { VersionConditionRender } from "./VersionConditionRender"; /** @@ -34,7 +34,7 @@ export const ReleaseConditionRender: React.FC< if (isMetadataCondition(condition)) return ( - +> = ({ condition, onChange, className }) => { + const { workspaceSlug, systemSlug } = useParams<{ + workspaceSlug: string; + systemSlug?: string; + }>(); + + const workspaceQ = api.workspace.bySlug.useQuery(workspaceSlug); + const workspace = workspaceQ.data; + const systemQ = api.system.bySlug.useQuery( + { workspaceSlug, systemSlug: systemSlug ?? "" }, + { enabled: systemSlug != null }, + ); + const system = systemQ.data; + + const workspaceMetadataKeys = api.release.metadataKeys.byWorkspace.useQuery( + workspace?.id ?? "", + { enabled: workspace != null && system == null }, + ); + const systemMetadataKeys = api.release.metadataKeys.bySystem.useQuery( + system?.id ?? "", + { enabled: system != null }, + ); + + const metadataKeys = + systemMetadataKeys.data ?? workspaceMetadataKeys.data ?? []; + + return ( + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/release-drawer/OverviewContent.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/release-drawer/OverviewContent.tsx index bdafbad7..e091b41e 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/release-drawer/OverviewContent.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/release-drawer/OverviewContent.tsx @@ -6,7 +6,7 @@ import { format } from "date-fns"; import yaml from "js-yaml"; import { Input } from "@ctrlplane/ui/input"; -import { ReservedMetadataKey } from "@ctrlplane/validators/targets"; +import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; import { useMatchSorterWithSearch } from "~/utils/useMatchSorter"; import { ConfigEditor } from "../ConfigEditor"; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/release-drawer/ReleaseDrawer.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/release-drawer/ReleaseDrawer.tsx index b530bc05..6439a517 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/release-drawer/ReleaseDrawer.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/release-drawer/ReleaseDrawer.tsx @@ -11,7 +11,7 @@ import { import { buttonVariants } from "@ctrlplane/ui/button"; import { Drawer, DrawerContent, DrawerTitle } from "@ctrlplane/ui/drawer"; -import { ReservedMetadataKey } from "@ctrlplane/validators/releases"; +import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; import { api } from "~/trpc/react"; import { TabButton } from "../TabButton"; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/KindConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/KindConditionRender.tsx index a67cd70a..9ee735a5 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/KindConditionRender.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/KindConditionRender.tsx @@ -1,26 +1,13 @@ import type { KindCondition } 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"; +import { ChoiceConditionRender } from "../filter/ChoiceConditionRender"; export const KindConditionRender: React.FC< TargetConditionRenderProps > = ({ condition, onChange, className }) => { - const [commandOpen, setCommandOpen] = useState(false); const { workspaceSlug } = useParams<{ workspaceSlug: string }>(); const workspace = api.workspace.bySlug.useQuery(workspaceSlug); const kinds = api.workspace.targetKinds.useQuery(workspace.data?.id ?? "", { @@ -29,60 +16,19 @@ export const KindConditionRender: React.FC< const setKind = (kind: string) => onChange({ ...condition, value: kind }); + const options = (kinds.data ?? []).map((kind) => ({ + key: kind, + value: kind, + display: kind, + })); + return ( -
-
-
- Kind -
-
- - - - - - - - - - {kinds.data?.length === 0 && ( - No kinds to add - )} - {kinds.data?.map((kind) => ( - { - setKind(kind); - setCommandOpen(false); - }} - > - {kind} - - ))} - - - - - -
-
-
+ ); }; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/ProviderConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/ProviderConditionRender.tsx index b3e81fbe..d010b0e4 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/ProviderConditionRender.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/ProviderConditionRender.tsx @@ -1,26 +1,13 @@ 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"; +import { ChoiceConditionRender } from "../filter/ChoiceConditionRender"; export const ProviderConditionRender: React.FC< TargetConditionRenderProps > = ({ 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( @@ -35,60 +22,19 @@ export const ProviderConditionRender: React.FC< (provider) => provider.id === condition.value, ); + const options = (providers.data ?? []).map((provider) => ({ + key: provider.id, + value: provider.id, + display: provider.name, + })); + return ( -
-
-
- Provider -
-
- - - - - - - - - - {providers.data?.length === 0 && ( - No providers to add - )} - {providers.data?.map((provider) => ( - { - setProvider(provider.id); - setCommandOpen(false); - }} - > - {provider.name} - - ))} - - - - - -
-
-
+ ); }; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/TargetConditionBadge.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/TargetConditionBadge.tsx index b0783f0b..108c5dc4 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/TargetConditionBadge.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/TargetConditionBadge.tsx @@ -1,7 +1,7 @@ +import type { MetadataCondition } from "@ctrlplane/validators/conditions"; import type { ComparisonCondition, KindCondition, - MetadataCondition, NameCondition, ProviderCondition, TargetCondition, diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/TargetConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/TargetConditionRender.tsx index 6cb9ea4e..61c6017e 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/TargetConditionRender.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/target-condition/TargetConditionRender.tsx @@ -12,9 +12,9 @@ import { 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"; +import { TargetMetadataConditionRender } from "./TargetMetadataConditionRender"; /** * The parent container should have min width of 1000px @@ -36,7 +36,7 @@ export const TargetConditionRender: React.FC< if (isMetadataCondition(condition)) return ( - +> = ({ condition, onChange, className }) => { + const { workspaceSlug } = useParams<{ workspaceSlug: string }>(); + const workspace = api.workspace.bySlug.useQuery(workspaceSlug); + const metadataKeys = api.target.metadataKeys.useQuery( + workspace.data?.id ?? "", + { enabled: workspace.isSuccess && workspace.data != null }, + ); + + return ( + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/OverviewContent.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/OverviewContent.tsx index 1233e9c7..22a168f2 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/OverviewContent.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/OverviewContent.tsx @@ -12,7 +12,7 @@ import { TooltipProvider, TooltipTrigger, } from "@ctrlplane/ui/tooltip"; -import { ReservedMetadataKey } from "@ctrlplane/validators/targets"; +import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; import { api } from "~/trpc/react"; import { useMatchSorterWithSearch } from "~/utils/useMatchSorter"; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/TargetDrawer.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/TargetDrawer.tsx index 8b192778..e42be098 100644 --- a/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/TargetDrawer.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/_components/target-drawer/TargetDrawer.tsx @@ -16,7 +16,7 @@ import { import { Button, buttonVariants } from "@ctrlplane/ui/button"; import { Drawer, DrawerContent, DrawerTitle } from "@ctrlplane/ui/drawer"; -import { ReservedMetadataKey } from "@ctrlplane/validators/targets"; +import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; import { api } from "~/trpc/react"; import { EditTargetDialog } from "../EditTarget"; diff --git a/apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/TargetReleaseTable.tsx b/apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/TargetReleaseTable.tsx index 0c05af31..48729a0f 100644 --- a/apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/TargetReleaseTable.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/TargetReleaseTable.tsx @@ -10,7 +10,7 @@ import _ from "lodash"; import { cn } from "@ctrlplane/ui"; import { Button, buttonVariants } from "@ctrlplane/ui/button"; import { Table, TableBody, TableCell, TableRow } from "@ctrlplane/ui/table"; -import { ReservedMetadataKey } from "@ctrlplane/validators/targets"; +import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; import { useJobDrawer } from "~/app/[workspaceSlug]/_components/job-drawer/useJobDrawer"; import { JobTableStatusIcon } from "~/app/[workspaceSlug]/_components/JobTableStatusIcon"; diff --git a/integrations/google-compute-scanner/src/gke.ts b/integrations/google-compute-scanner/src/gke.ts index c3a3f42a..53d324a1 100644 --- a/integrations/google-compute-scanner/src/gke.ts +++ b/integrations/google-compute-scanner/src/gke.ts @@ -8,10 +8,8 @@ import _ from "lodash"; import { SemVer } from "semver"; import { logger } from "@ctrlplane/logger"; -import { - kubernetesNamespaceV1, - ReservedMetadataKey, -} from "@ctrlplane/validators/targets"; +import { ReservedMetadataKey } from "@ctrlplane/validators/conditions"; +import { kubernetesNamespaceV1 } from "@ctrlplane/validators/targets"; import { env } from "./config.js"; import { connectToCluster } from "./gke-connect.js"; diff --git a/packages/db/src/schema/release.ts b/packages/db/src/schema/release.ts index dec84793..09b8996a 100644 --- a/packages/db/src/schema/release.ts +++ b/packages/db/src/schema/release.ts @@ -1,6 +1,8 @@ import type { CreatedAtCondition, MetadataCondition, +} from "@ctrlplane/validators/conditions"; +import type { ReleaseCondition, VersionCondition, } from "@ctrlplane/validators/releases"; diff --git a/packages/db/src/schema/target.ts b/packages/db/src/schema/target.ts index 2d0b18a0..b3d4dbf1 100644 --- a/packages/db/src/schema/target.ts +++ b/packages/db/src/schema/target.ts @@ -1,7 +1,5 @@ -import type { - MetadataCondition, - TargetCondition, -} from "@ctrlplane/validators/targets"; +import type { MetadataCondition } from "@ctrlplane/validators/conditions"; +import type { TargetCondition } from "@ctrlplane/validators/targets"; import type { InferInsertModel, InferSelectModel, SQL } from "drizzle-orm"; import { exists, like, not, notExists, or, relations, sql } from "drizzle-orm"; import { diff --git a/packages/validators/package.json b/packages/validators/package.json index 40b7b583..f19eda45 100644 --- a/packages/validators/package.json +++ b/packages/validators/package.json @@ -39,6 +39,10 @@ "./cac": { "types": "./src/cac/index.ts", "default": "./dist/cac/index.js" + }, + "./conditions": { + "types": "./src/conditions/index.ts", + "default": "./dist/conditions/index.js" } }, "license": "MIT", diff --git a/packages/validators/src/conditions/date-condition.ts b/packages/validators/src/conditions/date-condition.ts new file mode 100644 index 00000000..1c0e09c4 --- /dev/null +++ b/packages/validators/src/conditions/date-condition.ts @@ -0,0 +1,37 @@ +import { z } from "zod"; + +const operator = z.union([ + z.literal("before"), + z.literal("after"), + z.literal("before-or-on"), + z.literal("after-or-on"), +]); + +const isValidDate = (v: string) => !Number.isNaN(new Date(v).getTime()); +const value = z.string().refine(isValidDate, { message: "Invalid date" }); + +const createdAt = z.literal("created-at"); +const updatedAt = z.literal("updated-at"); + +export enum DateOperator { + Before = "before", + After = "after", + BeforeOrOn = "before-or-on", + AfterOrOn = "after-or-on", +} + +export const createdAtCondition = z.object({ + type: createdAt, + operator, + value, +}); + +export type CreatedAtCondition = z.infer; + +export const updatedAtCondition = z.object({ + type: updatedAt, + operator, + value, +}); + +export type UpdatedAtCondition = z.infer; diff --git a/packages/validators/src/conditions/index.ts b/packages/validators/src/conditions/index.ts new file mode 100644 index 00000000..ef3e3fb5 --- /dev/null +++ b/packages/validators/src/conditions/index.ts @@ -0,0 +1,2 @@ +export * from "./metadata-condition.js"; +export * from "./date-condition.js"; diff --git a/packages/validators/src/targets/conditions/metadata-condition.ts b/packages/validators/src/conditions/metadata-condition.ts similarity index 84% rename from packages/validators/src/targets/conditions/metadata-condition.ts rename to packages/validators/src/conditions/metadata-condition.ts index de33fc96..91d899ca 100644 --- a/packages/validators/src/targets/conditions/metadata-condition.ts +++ b/packages/validators/src/conditions/metadata-condition.ts @@ -45,6 +45,19 @@ export const metadataCondition = z.union([ export type MetadataCondition = z.infer; +export enum MetadataOperator { + Equals = "equals", + Like = "like", + Regex = "regex", + Null = "null", +} + +export type MetadataOperatorType = + | MetadataOperator.Equals + | MetadataOperator.Like + | MetadataOperator.Regex + | MetadataOperator.Null; + export enum ReservedMetadataKey { ExternalId = "ctrlplane/external-id", Links = "ctrlplane/links", diff --git a/packages/validators/src/releases/conditions/comparison-condition.ts b/packages/validators/src/releases/conditions/comparison-condition.ts index 337b32c5..8b2abdff 100644 --- a/packages/validators/src/releases/conditions/comparison-condition.ts +++ b/packages/validators/src/releases/conditions/comparison-condition.ts @@ -1,10 +1,10 @@ import { z } from "zod"; -import type { CreatedAtCondition } from "./created-at-condition.js"; -import type { MetadataCondition } from "./metadata-condition.js"; +import type { CreatedAtCondition } from "../../conditions/date-condition.js"; +import type { MetadataCondition } from "../../conditions/index.js"; import type { VersionCondition } from "./version-condition.js"; -import { createdAtCondition } from "./created-at-condition.js"; -import { metadataCondition } from "./metadata-condition.js"; +import { createdAtCondition } from "../../conditions/date-condition.js"; +import { metadataCondition } from "../../conditions/index.js"; import { versionCondition } from "./version-condition.js"; export const comparisonCondition: z.ZodType = z.lazy(() => diff --git a/packages/validators/src/releases/conditions/created-at-condition.ts b/packages/validators/src/releases/conditions/created-at-condition.ts deleted file mode 100644 index 92a35b20..00000000 --- a/packages/validators/src/releases/conditions/created-at-condition.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { z } from "zod"; - -export const createdAtCondition = z.object({ - type: z.literal("created-at"), - operator: z - .literal("before") - .or(z.literal("after")) - .or(z.literal("before-or-on")) - .or(z.literal("after-or-on")), - value: z.string().refine((v) => !isNaN(new Date(v).getTime()), { - message: "Invalid date", - }), -}); - -export type CreatedAtCondition = z.infer; diff --git a/packages/validators/src/releases/conditions/index.ts b/packages/validators/src/releases/conditions/index.ts index df1a12ac..3ee7679e 100644 --- a/packages/validators/src/releases/conditions/index.ts +++ b/packages/validators/src/releases/conditions/index.ts @@ -1,5 +1,3 @@ -export * from "./created-at-condition.js"; -export * from "./metadata-condition.js"; export * from "./version-condition.js"; export * from "./comparison-condition.js"; export * from "./release-condition.js"; diff --git a/packages/validators/src/releases/conditions/metadata-condition.ts b/packages/validators/src/releases/conditions/metadata-condition.ts deleted file mode 100644 index de33fc96..00000000 --- a/packages/validators/src/releases/conditions/metadata-condition.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { z } from "zod"; - -export const nullCondition = z.object({ - type: z.literal("metadata"), - key: z.string().min(1), - value: z.string().optional(), - operator: z.literal("null"), -}); - -export type NullCondition = z.infer; - -export const equalsCondition = z.object({ - type: z.literal("metadata"), - key: z.string().min(1), - value: z.string().min(1), - operator: z.literal("equals").optional(), -}); - -export type EqualCondition = z.infer; - -export const regexCondition = z.object({ - type: z.literal("metadata"), - key: z.string().min(1), - value: z.string().min(1), - operator: z.literal("regex"), -}); - -export type RegexCondition = z.infer; - -export const likeCondition = z.object({ - type: z.literal("metadata"), - key: z.string().min(1), - value: z.string().min(1), - operator: z.literal("like"), -}); - -export type LikeCondition = z.infer; - -export const metadataCondition = z.union([ - likeCondition, - regexCondition, - equalsCondition, - nullCondition, -]); - -export type MetadataCondition = z.infer; - -export enum ReservedMetadataKey { - ExternalId = "ctrlplane/external-id", - Links = "ctrlplane/links", - ParentTargetIdentifier = "ctrlplane/parent-target-identifier", - KubernetesVersion = "kubernetes/version", - KubernetesFlavor = "kubernetes/flavor", -} diff --git a/packages/validators/src/releases/conditions/release-condition.ts b/packages/validators/src/releases/conditions/release-condition.ts index 597d0aaf..6597e6cd 100644 --- a/packages/validators/src/releases/conditions/release-condition.ts +++ b/packages/validators/src/releases/conditions/release-condition.ts @@ -1,12 +1,12 @@ import { z } from "zod"; +import type { CreatedAtCondition } from "../../conditions/date-condition.js"; +import type { MetadataCondition } from "../../conditions/index.js"; import type { ComparisonCondition } from "./comparison-condition.js"; -import type { CreatedAtCondition } from "./created-at-condition.js"; -import type { MetadataCondition } from "./metadata-condition.js"; import type { VersionCondition } from "./version-condition.js"; +import { createdAtCondition } from "../../conditions/date-condition.js"; +import { metadataCondition } from "../../conditions/index.js"; import { comparisonCondition } from "./comparison-condition.js"; -import { createdAtCondition } from "./created-at-condition.js"; -import { metadataCondition } from "./metadata-condition.js"; import { versionCondition } from "./version-condition.js"; export type ReleaseCondition = diff --git a/packages/validators/src/targets/conditions/comparison-condition.ts b/packages/validators/src/targets/conditions/comparison-condition.ts index 5ac06518..0b91632d 100644 --- a/packages/validators/src/targets/conditions/comparison-condition.ts +++ b/packages/validators/src/targets/conditions/comparison-condition.ts @@ -1,11 +1,11 @@ import { z } from "zod"; +import type { MetadataCondition } from "../../conditions/index.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 { metadataCondition } from "../../conditions/index.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"; diff --git a/packages/validators/src/targets/conditions/index.ts b/packages/validators/src/targets/conditions/index.ts index 8cbf94cb..af520a47 100644 --- a/packages/validators/src/targets/conditions/index.ts +++ b/packages/validators/src/targets/conditions/index.ts @@ -1,4 +1,3 @@ -export * from "./metadata-condition.js"; export * from "./name-condition.js"; export * from "./kind-condition.js"; export * from "./comparison-condition.js"; diff --git a/packages/validators/src/targets/conditions/target-condition.ts b/packages/validators/src/targets/conditions/target-condition.ts index e98dbe61..d2ebad1b 100644 --- a/packages/validators/src/targets/conditions/target-condition.ts +++ b/packages/validators/src/targets/conditions/target-condition.ts @@ -1,13 +1,13 @@ import { z } from "zod"; +import type { MetadataCondition } from "../../conditions/index.js"; 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 { metadataCondition } from "../../conditions/index.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";