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 dace4b76..bde8edac 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
@@ -30,7 +30,11 @@ import {
SelectTrigger,
SelectValue,
} from "@ctrlplane/ui/select";
-import { ColumnOperator } from "@ctrlplane/validators/conditions";
+import {
+ ColumnOperator,
+ DateOperator,
+ FilterType,
+} from "@ctrlplane/validators/conditions";
import {
doesConvertingToComparisonRespectMaxDepth,
isComparisonCondition,
@@ -317,6 +321,18 @@ export const ComparisonConditionRender: React.FC<
>
Provider
+
+ addCondition({
+ type: FilterType.CreatedAt,
+ operator: DateOperator.Before,
+ value: new Date().toISOString(),
+ })
+ }
+ >
+ Created at
+
+
{depth < 2 && (
diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/ResourceCreatedAtConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/ResourceCreatedAtConditionRender.tsx
new file mode 100644
index 00000000..043b0b96
--- /dev/null
+++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/ResourceCreatedAtConditionRender.tsx
@@ -0,0 +1,34 @@
+import type {
+ CreatedAtCondition,
+ DateOperatorType,
+} from "@ctrlplane/validators/conditions";
+import type { DateValue } from "@internationalized/date";
+
+import type { TargetConditionRenderProps } from "./target-condition-props";
+import { DateConditionRender } from "../filter/DateConditionRender";
+
+export const ResourceCreatedAtConditionRender: React.FC<
+ TargetConditionRenderProps
+> = ({ condition, onChange, className }) => {
+ const setDate = (t: DateValue) =>
+ onChange({
+ ...condition,
+ value: t
+ .toDate(Intl.DateTimeFormat().resolvedOptions().timeZone)
+ .toISOString(),
+ });
+
+ const setOperator = (operator: DateOperatorType) =>
+ onChange({ ...condition, operator });
+
+ return (
+
+ );
+};
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 f0ae6d8f..a6d1a5d9 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
@@ -1,4 +1,7 @@
-import type { MetadataCondition } from "@ctrlplane/validators/conditions";
+import type {
+ CreatedAtCondition,
+ MetadataCondition,
+} from "@ctrlplane/validators/conditions";
import type {
ComparisonCondition,
IdentifierCondition,
@@ -8,6 +11,7 @@ import type {
ResourceCondition,
} from "@ctrlplane/validators/resources";
import React from "react";
+import { format } from "date-fns";
import _ from "lodash";
import { cn } from "@ctrlplane/ui";
@@ -17,9 +21,10 @@ import {
HoverCardContent,
HoverCardTrigger,
} from "@ctrlplane/ui/hover-card";
-import { ColumnOperator } from "@ctrlplane/validators/conditions";
+import { ColumnOperator, DateOperator } from "@ctrlplane/validators/conditions";
import {
isComparisonCondition,
+ isCreatedAtCondition,
isIdentifierCondition,
isKindCondition,
isMetadataCondition,
@@ -44,6 +49,10 @@ const operatorVerbs = {
[ColumnOperator.StartsWith]: "starts with",
[ColumnOperator.EndsWith]: "ends with",
[ColumnOperator.Contains]: "contains",
+ [DateOperator.Before]: "before",
+ [DateOperator.After]: "after",
+ [DateOperator.BeforeOrOn]: "before or on",
+ [DateOperator.AfterOrOn]: "after or on",
};
const ConditionBadge: React.FC<{
@@ -208,6 +217,20 @@ const StringifiedProviderCondition: React.FC<{
);
};
+const StringifiedCreatedAtCondition: React.FC<{
+ condition: CreatedAtCondition;
+}> = ({ condition }) => (
+
+ created
+
+ {operatorVerbs[condition.operator]}
+
+
+ {format(condition.value, "MMM d, yyyy, h:mma")}
+
+
+);
+
const StringifiedTargetCondition: React.FC<{
condition: ResourceCondition;
depth?: number;
@@ -242,6 +265,9 @@ const StringifiedTargetCondition: React.FC<{
if (isProviderCondition(condition))
return ;
+
+ if (isCreatedAtCondition(condition))
+ return ;
};
export const TargetConditionBadge: React.FC<{
diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/TargetConditionRender.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/TargetConditionRender.tsx
index ccad6dc2..befaccf0 100644
--- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/TargetConditionRender.tsx
+++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-condition/TargetConditionRender.tsx
@@ -3,6 +3,7 @@ import React from "react";
import {
isComparisonCondition,
+ isCreatedAtCondition,
isIdentifierCondition,
isKindCondition,
isMetadataCondition,
@@ -16,6 +17,7 @@ import { IdentifierConditionRender } from "./IdentifierConditionRender";
import { KindConditionRender } from "./KindConditionRender";
import { NameConditionRender } from "./NameConditionRender";
import { ProviderConditionRender } from "./ProviderConditionRender";
+import { ResourceCreatedAtConditionRender } from "./ResourceCreatedAtConditionRender";
import { TargetMetadataConditionRender } from "./TargetMetadataConditionRender";
/**
@@ -80,5 +82,14 @@ export const TargetConditionRender: React.FC<
/>
);
+ if (isCreatedAtCondition(condition))
+ return (
+
+ );
+
return null;
};
diff --git a/packages/db/src/schema/resource.ts b/packages/db/src/schema/resource.ts
index ecb59e0f..8ba698af 100644
--- a/packages/db/src/schema/resource.ts
+++ b/packages/db/src/schema/resource.ts
@@ -1,4 +1,7 @@
-import type { MetadataCondition } from "@ctrlplane/validators/conditions";
+import type {
+ CreatedAtCondition,
+ MetadataCondition,
+} from "@ctrlplane/validators/conditions";
import type {
IdentifierCondition,
NameCondition,
@@ -7,8 +10,12 @@ import type {
import type { InferInsertModel, InferSelectModel, SQL } from "drizzle-orm";
import {
exists,
+ gt,
+ gte,
ilike,
like,
+ lt,
+ lte,
not,
notExists,
or,
@@ -33,6 +40,8 @@ import { z } from "zod";
import {
ColumnOperator,
ComparisonOperator,
+ DateOperator,
+ FilterType,
MetadataOperator,
} from "@ctrlplane/validators/conditions";
import {
@@ -243,6 +252,16 @@ const buildNameCondition = (tx: Tx, cond: NameCondition): SQL => {
return sql`${resource.name} ~ ${cond.value}`;
};
+const buildCreatedAtCondition = (tx: Tx, cond: CreatedAtCondition): SQL => {
+ const date = new Date(cond.value);
+ if (cond.operator === DateOperator.Before)
+ return lt(resource.createdAt, date);
+ if (cond.operator === DateOperator.After) return gt(resource.createdAt, date);
+ if (cond.operator === DateOperator.BeforeOrOn)
+ return lte(resource.createdAt, date);
+ return gte(resource.createdAt, date);
+};
+
const buildCondition = (tx: Tx, cond: ResourceCondition): SQL => {
if (cond.type === ResourceFilterType.Metadata)
return buildMetadataCondition(tx, cond);
@@ -254,6 +273,8 @@ const buildCondition = (tx: Tx, cond: ResourceCondition): SQL => {
return eq(resource.providerId, cond.value);
if (cond.type === ResourceFilterType.Identifier)
return buildIdentifierCondition(tx, cond);
+ if (cond.type === FilterType.CreatedAt)
+ return buildCreatedAtCondition(tx, cond);
if (cond.conditions.length === 0) return sql`FALSE`;
diff --git a/packages/validators/src/resources/conditions/comparison-condition.ts b/packages/validators/src/resources/conditions/comparison-condition.ts
index a5e85a19..5060b3a2 100644
--- a/packages/validators/src/resources/conditions/comparison-condition.ts
+++ b/packages/validators/src/resources/conditions/comparison-condition.ts
@@ -1,11 +1,17 @@
import { z } from "zod";
-import type { MetadataCondition } from "../../conditions/index.js";
+import type {
+ CreatedAtCondition,
+ MetadataCondition,
+} from "../../conditions/index.js";
import type { IdentifierCondition } from "./identifier-condition.js";
import type { KindCondition } from "./kind-condition.js";
import type { NameCondition } from "./name-condition.js";
import type { ProviderCondition } from "./provider-condition.js";
-import { metadataCondition } from "../../conditions/index.js";
+import {
+ createdAtCondition,
+ metadataCondition,
+} from "../../conditions/index.js";
import { identifierCondition } from "./identifier-condition.js";
import { kindCondition } from "./kind-condition.js";
import { nameCondition } from "./name-condition.js";
@@ -24,6 +30,7 @@ export const comparisonCondition: z.ZodType = z.lazy(() =>
nameCondition,
providerCondition,
identifierCondition,
+ createdAtCondition,
]),
),
}),
@@ -40,5 +47,6 @@ export type ComparisonCondition = {
| NameCondition
| ProviderCondition
| IdentifierCondition
+ | CreatedAtCondition
>;
};
diff --git a/packages/validators/src/resources/conditions/resource-condition.ts b/packages/validators/src/resources/conditions/resource-condition.ts
index 71ee1799..f3c7868f 100644
--- a/packages/validators/src/resources/conditions/resource-condition.ts
+++ b/packages/validators/src/resources/conditions/resource-condition.ts
@@ -1,12 +1,19 @@
import { z } from "zod";
-import type { MetadataCondition } from "../../conditions/index.js";
+import type {
+ CreatedAtCondition,
+ MetadataCondition,
+} from "../../conditions/index.js";
import type { ComparisonCondition } from "./comparison-condition.js";
import type { IdentifierCondition } from "./identifier-condition.js";
import type { KindCondition } from "./kind-condition.js";
import type { NameCondition } from "./name-condition.js";
import type { ProviderCondition } from "./provider-condition.js";
-import { metadataCondition } from "../../conditions/index.js";
+import {
+ createdAtCondition,
+ FilterType,
+ metadataCondition,
+} from "../../conditions/index.js";
import { comparisonCondition } from "./comparison-condition.js";
import { identifierCondition } from "./identifier-condition.js";
import { kindCondition } from "./kind-condition.js";
@@ -19,7 +26,8 @@ export type ResourceCondition =
| KindCondition
| NameCondition
| ProviderCondition
- | IdentifierCondition;
+ | IdentifierCondition
+ | CreatedAtCondition;
export const resourceCondition = z.union([
comparisonCondition,
@@ -28,6 +36,7 @@ export const resourceCondition = z.union([
nameCondition,
providerCondition,
identifierCondition,
+ createdAtCondition,
]);
export enum ResourceOperator {
@@ -104,6 +113,10 @@ export const isIdentifierCondition = (
): condition is IdentifierCondition =>
condition.type === ResourceFilterType.Identifier;
+export const isCreatedAtCondition = (
+ condition: ResourceCondition,
+): condition is CreatedAtCondition => condition.type === FilterType.CreatedAt;
+
export const isValidTargetCondition = (
condition: ResourceCondition,
): boolean => {