From ce94c33013e85eada5f7921aab0264a54135c32d Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Sun, 22 Dec 2024 13:26:47 -0500 Subject: [PATCH 1/6] init --- .../RolloutAndTiming.tsx | 54 +- .../db/drizzle/0048_lucky_eddie_brock.sql | 1 + packages/db/drizzle/meta/0048_snapshot.json | 4771 +++++++++++++++++ packages/db/drizzle/meta/_journal.json | 9 +- packages/db/src/schema/environment.ts | 7 + .../policies/min-release-interval-policy.ts | 161 + packages/job-dispatch/src/policy-checker.ts | 32 +- 7 files changed, 5010 insertions(+), 25 deletions(-) create mode 100644 packages/db/drizzle/0048_lucky_eddie_brock.sql create mode 100644 packages/db/drizzle/meta/0048_snapshot.json create mode 100644 packages/job-dispatch/src/policies/min-release-interval-policy.ts diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx index bf2891fb..d7745f59 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx @@ -48,6 +48,9 @@ const schema = z.object({ rolloutDuration: z.string().refine(isValidDuration, { message: "Invalid duration pattern", }), + minimumReleaseInterval: z.string().refine(isValidDuration, { + message: "Invalid duration pattern", + }), }); type RolloutAndTimingProps = { @@ -62,10 +65,15 @@ export const RolloutAndTiming: React.FC = ({ isLoading, }) => { const rolloutDuration = prettyMilliseconds(environmentPolicy.rolloutDuration); - const form = useForm({ - schema, - defaultValues: { ...environmentPolicy, rolloutDuration }, - }); + const minimumReleaseInterval = prettyMilliseconds( + environmentPolicy.minimumReleaseInterval, + ); + const defaultValues = { + ...environmentPolicy, + rolloutDuration, + minimumReleaseInterval, + }; + const form = useForm({ schema, defaultValues }); const { fields, append, remove } = useFieldArray({ control: form.control, @@ -79,8 +87,10 @@ export const RolloutAndTiming: React.FC = ({ const onSubmit = form.handleSubmit((data) => { const { releaseWindows, rolloutDuration: durationString } = data; const rolloutDuration = ms(durationString); + const minimumReleaseInterval = ms(data.minimumReleaseInterval); + const updates = { rolloutDuration, releaseWindows, minimumReleaseInterval }; updatePolicy - .mutateAsync({ id: policyId, data: { rolloutDuration, releaseWindows } }) + .mutateAsync({ id: policyId, data: updates }) .then(() => form.reset(data)) .then(() => invalidatePolicy()) .catch((e) => toast.error(e.message)); @@ -222,7 +232,39 @@ export const RolloutAndTiming: React.FC = ({
- Spread deployments out over + Roll deployments out over + + +
+ +
+ + + )} + /> + + ( + +
+ Minimum Release Interval + + Setting a minimum release interval will ensure that a certain + amount of time has passed since the last active release. + +
+ +
+
+ + Minimum amount of time between active releases: + db + .select({ + id: SCHEMA.release.id, + deploymentId: SCHEMA.release.deploymentId, + version: SCHEMA.release.version, + createdAt: SCHEMA.release.createdAt, + name: SCHEMA.release.name, + config: SCHEMA.release.config, + environmentId: SCHEMA.environment.id, + rank: sql`ROW_NUMBER() OVER (PARTITION BY ${SCHEMA.release.deploymentId}, ${SCHEMA.releaseJobTrigger.environmentId} ORDER BY ${SCHEMA.release.createdAt} DESC)`.as( + "rank", + ), + }) + .from(SCHEMA.release) + .innerJoin( + SCHEMA.environment, + inArray(SCHEMA.environment.id, environmentIds), + ) + .where( + and( + exists( + db + .select() + .from(SCHEMA.releaseJobTrigger) + .where( + and( + eq(SCHEMA.releaseJobTrigger.releaseId, SCHEMA.release.id), + eq( + SCHEMA.releaseJobTrigger.environmentId, + SCHEMA.environment.id, + ), + ), + ) + .limit(1), + ), + notExists( + db + .select() + .from(SCHEMA.releaseJobTrigger) + .where( + and( + eq(SCHEMA.releaseJobTrigger.releaseId, SCHEMA.release.id), + eq( + SCHEMA.releaseJobTrigger.environmentId, + SCHEMA.environment.id, + ), + inArray(SCHEMA.job.status, [ + ...activeStatus, + JobStatus.Pending, + ]), + ), + ), + ), + ), + ) + .as("latest_completed_release"); + +/** + * + * @param db + * @param releaseJobTriggers + * @returns ReleaseJobTriggers that pass the min release interval policy + * ensuring that the minimum interval between releases is respected. + */ +export const isPassingMinReleaseIntervalPolicy: ReleaseIdPolicyChecker = async ( + db, + releaseJobTriggers, +) => { + if (releaseJobTriggers.length === 0) return []; + + const releases = await db + .select() + .from(SCHEMA.release) + .where( + inArray( + SCHEMA.release.id, + releaseJobTriggers.map((rjt) => rjt.releaseId), + ), + ); + + const environmentIds = releaseJobTriggers.map((rjt) => rjt.environmentId); + + const latestCompletedReleasesSubquery = latestCompletedReleaseSubQuery( + db, + environmentIds, + ); + + const environments = await db + .select() + .from(SCHEMA.environment) + .innerJoin( + SCHEMA.environmentPolicy, + eq(SCHEMA.environment.policyId, SCHEMA.environmentPolicy.id), + ) + .leftJoin( + latestCompletedReleasesSubquery, + eq(latestCompletedReleasesSubquery.environmentId, SCHEMA.environment.id), + ) + .where( + and( + inArray( + latestCompletedReleasesSubquery.deploymentId, + releases.map((r) => r.deploymentId), + ), + eq(latestCompletedReleasesSubquery.rank, 1), + ), + ) + .then((rows) => + _.chain(rows) + .groupBy((r) => r.environment.id) + .map((groupedRows) => ({ + ...groupedRows[0]!.environment, + policy: groupedRows[0]!.environment_policy, + latestCompletedReleases: groupedRows + .filter((r) => isPresent(r.latest_completed_release)) + .map((r) => r.latest_completed_release!), + })) + .value(), + ); + + return _.chain(releaseJobTriggers) + .groupBy((rjt) => [rjt.environmentId, rjt.releaseId]) + .filter((groupedTriggers) => { + const release = releases.find( + (r) => r.deploymentId === groupedTriggers[0]!.releaseId, + ); + if (!release) return false; + + const environment = environments.find( + (e) => e.id === groupedTriggers[0]!.environmentId, + ); + if (!environment) return true; + + const latestCompletedRelease = environment.latestCompletedReleases.find( + (r) => r.deploymentId === release.deploymentId, + ); + if (!latestCompletedRelease) return true; + + const { minimumReleaseInterval } = environment.policy; + const timeSinceLatestCompletedRelease = differenceInMilliseconds( + release.createdAt, + latestCompletedRelease.createdAt, + ); + + return timeSinceLatestCompletedRelease >= minimumReleaseInterval; + }) + .value() + .flat(); +}; diff --git a/packages/job-dispatch/src/policy-checker.ts b/packages/job-dispatch/src/policy-checker.ts index 65824c04..8e643f86 100644 --- a/packages/job-dispatch/src/policy-checker.ts +++ b/packages/job-dispatch/src/policy-checker.ts @@ -7,6 +7,7 @@ import { isPassingLockingPolicy } from "./lock-checker.js"; import { isPassingConcurrencyPolicy } from "./policies/concurrency-policy.js"; import { isPassingJobRolloutPolicy } from "./policies/gradual-rollout.js"; import { isPassingApprovalPolicy } from "./policies/manual-approval.js"; +import { isPassingMinReleaseIntervalPolicy } from "./policies/min-release-interval-policy.js"; import { isPassingNewerThanLastActiveReleasePolicy, isPassingNoActiveJobsPolicy, @@ -14,22 +15,26 @@ import { import { isPassingReleaseWindowPolicy } from "./policies/release-window.js"; import { isPassingCriteriaPolicy } from "./policies/success-rate-criteria-passing.js"; +const baseChecks: ReleaseIdPolicyChecker[] = [ + isPassingLockingPolicy, + isPassingApprovalPolicy, + isPassingCriteriaPolicy, + isPassingConcurrencyPolicy, + isPassingJobRolloutPolicy, + isPassingNoActiveJobsPolicy, + isPassingReleaseWindowPolicy, + isPassingMinReleaseIntervalPolicy, +]; + export const isPassingAllPolicies = async ( db: Tx, releaseJobTriggers: schema.ReleaseJobTrigger[], ) => { if (releaseJobTriggers.length === 0) return []; const checks: ReleaseIdPolicyChecker[] = [ - isPassingLockingPolicy, - isPassingApprovalPolicy, - isPassingCriteriaPolicy, - isPassingConcurrencyPolicy, - isPassingJobRolloutPolicy, - isPassingNoActiveJobsPolicy, + ...baseChecks, isPassingNewerThanLastActiveReleasePolicy, - isPassingReleaseWindowPolicy, ]; - let passingJobs = releaseJobTriggers; for (const check of checks) passingJobs = await check(db, passingJobs); @@ -41,18 +46,9 @@ export const isPassingAllPoliciesExceptNewerThanLastActive = async ( releaseJobTriggers: schema.ReleaseJobTrigger[], ) => { if (releaseJobTriggers.length === 0) return []; - const checks: ReleaseIdPolicyChecker[] = [ - isPassingLockingPolicy, - isPassingApprovalPolicy, - isPassingCriteriaPolicy, - isPassingConcurrencyPolicy, - isPassingJobRolloutPolicy, - isPassingNoActiveJobsPolicy, - isPassingReleaseWindowPolicy, - ]; let passingJobs = releaseJobTriggers; - for (const check of checks) passingJobs = await check(db, passingJobs); + for (const check of baseChecks) passingJobs = await check(db, passingJobs); return passingJobs; }; From b6a6bfa3a2877786dd2b3ef735e2cc48a0de3a3f Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Sun, 22 Dec 2024 14:54:16 -0500 Subject: [PATCH 2/6] fix query --- .../policies/min-release-interval-policy.ts | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/packages/job-dispatch/src/policies/min-release-interval-policy.ts b/packages/job-dispatch/src/policies/min-release-interval-policy.ts index e620c2c1..de548041 100644 --- a/packages/job-dispatch/src/policies/min-release-interval-policy.ts +++ b/packages/job-dispatch/src/policies/min-release-interval-policy.ts @@ -3,7 +3,16 @@ import { differenceInMilliseconds } from "date-fns"; import _ from "lodash"; import { isPresent } from "ts-is-present"; -import { and, eq, exists, inArray, notExists, sql } from "@ctrlplane/db"; +import { + and, + eq, + exists, + inArray, + isNull, + notExists, + or, + sql, +} from "@ctrlplane/db"; import * as SCHEMA from "@ctrlplane/db/schema"; import { activeStatus, JobStatus } from "@ctrlplane/validators/jobs"; @@ -12,14 +21,10 @@ import type { ReleaseIdPolicyChecker } from "./utils"; const latestCompletedReleaseSubQuery = (db: Tx, environmentIds: string[]) => db .select({ - id: SCHEMA.release.id, deploymentId: SCHEMA.release.deploymentId, - version: SCHEMA.release.version, createdAt: SCHEMA.release.createdAt, - name: SCHEMA.release.name, - config: SCHEMA.release.config, environmentId: SCHEMA.environment.id, - rank: sql`ROW_NUMBER() OVER (PARTITION BY ${SCHEMA.release.deploymentId}, ${SCHEMA.releaseJobTrigger.environmentId} ORDER BY ${SCHEMA.release.createdAt} DESC)`.as( + rank: sql`ROW_NUMBER() OVER (PARTITION BY ${SCHEMA.release.deploymentId}, ${SCHEMA.environment.id} ORDER BY ${SCHEMA.release.createdAt} DESC)`.as( "rank", ), }) @@ -49,6 +54,10 @@ const latestCompletedReleaseSubQuery = (db: Tx, environmentIds: string[]) => db .select() .from(SCHEMA.releaseJobTrigger) + .innerJoin( + SCHEMA.job, + eq(SCHEMA.releaseJobTrigger.jobId, SCHEMA.job.id), + ) .where( and( eq(SCHEMA.releaseJobTrigger.releaseId, SCHEMA.release.id), @@ -110,11 +119,17 @@ export const isPassingMinReleaseIntervalPolicy: ReleaseIdPolicyChecker = async ( ) .where( and( - inArray( - latestCompletedReleasesSubquery.deploymentId, - releases.map((r) => r.deploymentId), + or( + isNull(latestCompletedReleasesSubquery.deploymentId), + and( + inArray( + latestCompletedReleasesSubquery.deploymentId, + releases.map((r) => r.deploymentId), + ), + eq(latestCompletedReleasesSubquery.rank, 1), + ), ), - eq(latestCompletedReleasesSubquery.rank, 1), + inArray(SCHEMA.environment.id, environmentIds), ), ) .then((rows) => @@ -134,7 +149,7 @@ export const isPassingMinReleaseIntervalPolicy: ReleaseIdPolicyChecker = async ( .groupBy((rjt) => [rjt.environmentId, rjt.releaseId]) .filter((groupedTriggers) => { const release = releases.find( - (r) => r.deploymentId === groupedTriggers[0]!.releaseId, + (r) => r.id === groupedTriggers[0]!.releaseId, ); if (!release) return false; @@ -150,7 +165,7 @@ export const isPassingMinReleaseIntervalPolicy: ReleaseIdPolicyChecker = async ( const { minimumReleaseInterval } = environment.policy; const timeSinceLatestCompletedRelease = differenceInMilliseconds( - release.createdAt, + new Date(), latestCompletedRelease.createdAt, ); From e7ce8cbd964cf5788ddd2aa78d18405a8a061731 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Sun, 22 Dec 2024 14:55:02 -0500 Subject: [PATCH 3/6] format --- packages/db/drizzle/meta/0048_snapshot.json | 726 +++++--------------- packages/db/drizzle/meta/_journal.json | 2 +- 2 files changed, 180 insertions(+), 548 deletions(-) diff --git a/packages/db/drizzle/meta/0048_snapshot.json b/packages/db/drizzle/meta/0048_snapshot.json index ff819ec8..b5c3aaad 100644 --- a/packages/db/drizzle/meta/0048_snapshot.json +++ b/packages/db/drizzle/meta/0048_snapshot.json @@ -81,12 +81,8 @@ "name": "account_userId_user_id_fk", "tableFrom": "account", "tableTo": "user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["userId"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -94,10 +90,7 @@ "compositePrimaryKeys": { "account_provider_providerAccountId_pk": { "name": "account_provider_providerAccountId_pk", - "columns": [ - "provider", - "providerAccountId" - ] + "columns": ["provider", "providerAccountId"] } }, "uniqueConstraints": {} @@ -131,12 +124,8 @@ "name": "session_userId_user_id_fk", "tableFrom": "session", "tableTo": "user", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["userId"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -200,12 +189,8 @@ "name": "user_active_workspace_id_workspace_id_fk", "tableFrom": "user", "tableTo": "workspace", - "columnsFrom": [ - "active_workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["active_workspace_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -289,12 +274,8 @@ "name": "user_api_key_user_id_user_id_fk", "tableFrom": "user_api_key", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -351,12 +332,8 @@ "name": "dashboard_workspace_id_workspace_id_fk", "tableFrom": "dashboard", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -425,12 +402,8 @@ "name": "dashboard_widget_dashboard_id_dashboard_id_fk", "tableFrom": "dashboard_widget", "tableTo": "dashboard", - "columnsFrom": [ - "dashboard_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["dashboard_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -510,12 +483,8 @@ "name": "deployment_variable_deployment_id_deployment_id_fk", "tableFrom": "deployment_variable", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -523,12 +492,8 @@ "name": "deployment_variable_default_value_id_deployment_variable_value_id_fk", "tableFrom": "deployment_variable", "tableTo": "deployment_variable_value", - "columnsFrom": [ - "default_value_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["default_value_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -588,12 +553,8 @@ "name": "deployment_variable_set_deployment_id_deployment_id_fk", "tableFrom": "deployment_variable_set", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -601,12 +562,8 @@ "name": "deployment_variable_set_variable_set_id_variable_set_id_fk", "tableFrom": "deployment_variable_set", "tableTo": "variable_set", - "columnsFrom": [ - "variable_set_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["variable_set_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -673,12 +630,8 @@ "name": "deployment_variable_value_variable_id_deployment_variable_id_fk", "tableFrom": "deployment_variable_value", "tableTo": "deployment_variable", - "columnsFrom": [ - "variable_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["variable_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "restrict" } @@ -770,12 +723,8 @@ "name": "deployment_system_id_system_id_fk", "tableFrom": "deployment", "tableTo": "system", - "columnsFrom": [ - "system_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["system_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -783,12 +732,8 @@ "name": "deployment_job_agent_id_job_agent_id_fk", "tableFrom": "deployment", "tableTo": "job_agent", - "columnsFrom": [ - "job_agent_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_agent_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -847,12 +792,8 @@ "name": "deployment_meta_dependency_deployment_id_deployment_id_fk", "tableFrom": "deployment_meta_dependency", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -860,12 +801,8 @@ "name": "deployment_meta_dependency_depends_on_id_deployment_id_fk", "tableFrom": "deployment_meta_dependency", "tableTo": "deployment", - "columnsFrom": [ - "depends_on_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["depends_on_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -959,12 +896,8 @@ "name": "environment_system_id_system_id_fk", "tableFrom": "environment", "tableTo": "system", - "columnsFrom": [ - "system_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["system_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -972,12 +905,8 @@ "name": "environment_policy_id_environment_policy_id_fk", "tableFrom": "environment", "tableTo": "environment_policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -1043,12 +972,8 @@ "name": "environment_metadata_environment_id_environment_id_fk", "tableFrom": "environment_metadata", "tableTo": "environment", - "columnsFrom": [ - "environment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1144,12 +1069,8 @@ "name": "environment_policy_system_id_system_id_fk", "tableFrom": "environment_policy", "tableTo": "system", - "columnsFrom": [ - "system_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["system_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -1223,12 +1144,8 @@ "name": "environment_policy_approval_policy_id_environment_policy_id_fk", "tableFrom": "environment_policy_approval", "tableTo": "environment_policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1236,12 +1153,8 @@ "name": "environment_policy_approval_release_id_release_id_fk", "tableFrom": "environment_policy_approval", "tableTo": "release", - "columnsFrom": [ - "release_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["release_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1249,12 +1162,8 @@ "name": "environment_policy_approval_user_id_user_id_fk", "tableFrom": "environment_policy_approval", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -1314,12 +1223,8 @@ "name": "environment_policy_deployment_policy_id_environment_policy_id_fk", "tableFrom": "environment_policy_deployment", "tableTo": "environment_policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1327,12 +1232,8 @@ "name": "environment_policy_deployment_environment_id_environment_id_fk", "tableFrom": "environment_policy_deployment", "tableTo": "environment", - "columnsFrom": [ - "environment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1419,12 +1320,8 @@ "name": "environment_policy_release_channel_policy_id_environment_policy_id_fk", "tableFrom": "environment_policy_release_channel", "tableTo": "environment_policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1432,12 +1329,8 @@ "name": "environment_policy_release_channel_channel_id_release_channel_id_fk", "tableFrom": "environment_policy_release_channel", "tableTo": "release_channel", - "columnsFrom": [ - "channel_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["channel_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1445,12 +1338,8 @@ "name": "environment_policy_release_channel_deployment_id_deployment_id_fk", "tableFrom": "environment_policy_release_channel", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1501,12 +1390,8 @@ "name": "environment_policy_release_window_policy_id_environment_policy_id_fk", "tableFrom": "environment_policy_release_window", "tableTo": "environment_policy", - "columnsFrom": [ - "policy_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["policy_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1593,12 +1478,8 @@ "name": "environment_release_channel_environment_id_environment_id_fk", "tableFrom": "environment_release_channel", "tableTo": "environment", - "columnsFrom": [ - "environment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1606,12 +1487,8 @@ "name": "environment_release_channel_channel_id_release_channel_id_fk", "tableFrom": "environment_release_channel", "tableTo": "release_channel", - "columnsFrom": [ - "channel_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["channel_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1619,12 +1496,8 @@ "name": "environment_release_channel_deployment_id_deployment_id_fk", "tableFrom": "environment_release_channel", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1761,12 +1634,8 @@ "name": "runhook_hook_id_hook_id_fk", "tableFrom": "runhook", "tableTo": "hook", - "columnsFrom": [ - "hook_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["hook_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1774,12 +1643,8 @@ "name": "runhook_runbook_id_runbook_id_fk", "tableFrom": "runhook", "tableTo": "runbook", - "columnsFrom": [ - "runbook_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["runbook_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1871,12 +1736,8 @@ "name": "github_organization_added_by_user_id_user_id_fk", "tableFrom": "github_organization", "tableTo": "user", - "columnsFrom": [ - "added_by_user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["added_by_user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -1884,12 +1745,8 @@ "name": "github_organization_workspace_id_workspace_id_fk", "tableFrom": "github_organization", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1933,12 +1790,8 @@ "name": "github_user_user_id_user_id_fk", "tableFrom": "github_user", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -1998,12 +1851,8 @@ "name": "job_resource_relationship_job_id_job_id_fk", "tableFrom": "job_resource_relationship", "tableTo": "job", - "columnsFrom": [ - "job_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2119,12 +1968,8 @@ "name": "resource_provider_id_resource_provider_id_fk", "tableFrom": "resource", "tableTo": "resource_provider", - "columnsFrom": [ - "provider_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["provider_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" }, @@ -2132,12 +1977,8 @@ "name": "resource_workspace_id_workspace_id_fk", "tableFrom": "resource", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2203,12 +2044,8 @@ "name": "resource_metadata_resource_id_resource_id_fk", "tableFrom": "resource_metadata", "tableTo": "resource", - "columnsFrom": [ - "resource_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2281,12 +2118,8 @@ "name": "resource_relationship_workspace_id_workspace_id_fk", "tableFrom": "resource_relationship", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2364,12 +2197,8 @@ "name": "resource_schema_workspace_id_workspace_id_fk", "tableFrom": "resource_schema", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -2442,12 +2271,8 @@ "name": "resource_variable_resource_id_resource_id_fk", "tableFrom": "resource_variable", "tableTo": "resource", - "columnsFrom": [ - "resource_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2498,12 +2323,8 @@ "name": "resource_view_workspace_id_workspace_id_fk", "tableFrom": "resource_view", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2570,12 +2391,8 @@ "name": "resource_provider_workspace_id_workspace_id_fk", "tableFrom": "resource_provider", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -2613,12 +2430,8 @@ "name": "resource_provider_aws_resource_provider_id_resource_provider_id_fk", "tableFrom": "resource_provider_aws", "tableTo": "resource_provider", - "columnsFrom": [ - "resource_provider_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["resource_provider_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2677,12 +2490,8 @@ "name": "resource_provider_google_resource_provider_id_resource_provider_id_fk", "tableFrom": "resource_provider_google", "tableTo": "resource_provider", - "columnsFrom": [ - "resource_provider_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["resource_provider_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2762,12 +2571,8 @@ "name": "release_deployment_id_deployment_id_fk", "tableFrom": "release", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2841,12 +2646,8 @@ "name": "release_channel_deployment_id_deployment_id_fk", "tableFrom": "release_channel", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -2913,12 +2714,8 @@ "name": "release_dependency_release_id_release_id_fk", "tableFrom": "release_dependency", "tableTo": "release", - "columnsFrom": [ - "release_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["release_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -2926,12 +2723,8 @@ "name": "release_dependency_deployment_id_deployment_id_fk", "tableFrom": "release_dependency", "tableTo": "deployment", - "columnsFrom": [ - "deployment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["deployment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3001,12 +2794,8 @@ "name": "release_job_trigger_job_id_job_id_fk", "tableFrom": "release_job_trigger", "tableTo": "job", - "columnsFrom": [ - "job_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -3014,12 +2803,8 @@ "name": "release_job_trigger_caused_by_id_user_id_fk", "tableFrom": "release_job_trigger", "tableTo": "user", - "columnsFrom": [ - "caused_by_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["caused_by_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" }, @@ -3027,12 +2812,8 @@ "name": "release_job_trigger_release_id_release_id_fk", "tableFrom": "release_job_trigger", "tableTo": "release", - "columnsFrom": [ - "release_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["release_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3040,12 +2821,8 @@ "name": "release_job_trigger_resource_id_resource_id_fk", "tableFrom": "release_job_trigger", "tableTo": "resource", - "columnsFrom": [ - "resource_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["resource_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3053,12 +2830,8 @@ "name": "release_job_trigger_environment_id_environment_id_fk", "tableFrom": "release_job_trigger", "tableTo": "environment", - "columnsFrom": [ - "environment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3068,9 +2841,7 @@ "release_job_trigger_job_id_unique": { "name": "release_job_trigger_job_id_unique", "nullsNotDistinct": false, - "columns": [ - "job_id" - ] + "columns": ["job_id"] } } }, @@ -3132,12 +2903,8 @@ "name": "release_metadata_release_id_release_id_fk", "tableFrom": "release_metadata", "tableTo": "release", - "columnsFrom": [ - "release_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["release_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3210,12 +2977,8 @@ "name": "system_workspace_id_workspace_id_fk", "tableFrom": "system", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3272,12 +3035,8 @@ "name": "runbook_system_id_system_id_fk", "tableFrom": "runbook", "tableTo": "system", - "columnsFrom": [ - "system_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["system_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3285,12 +3044,8 @@ "name": "runbook_job_agent_id_job_agent_id_fk", "tableFrom": "runbook", "tableTo": "job_agent", - "columnsFrom": [ - "job_agent_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_agent_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -3335,12 +3090,8 @@ "name": "runbook_job_trigger_job_id_job_id_fk", "tableFrom": "runbook_job_trigger", "tableTo": "job", - "columnsFrom": [ - "job_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3348,12 +3099,8 @@ "name": "runbook_job_trigger_runbook_id_runbook_id_fk", "tableFrom": "runbook_job_trigger", "tableTo": "runbook", - "columnsFrom": [ - "runbook_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["runbook_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3363,9 +3110,7 @@ "runbook_job_trigger_job_id_unique": { "name": "runbook_job_trigger_job_id_unique", "nullsNotDistinct": false, - "columns": [ - "job_id" - ] + "columns": ["job_id"] } } }, @@ -3399,12 +3144,8 @@ "name": "team_workspace_id_workspace_id_fk", "tableFrom": "team", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3464,12 +3205,8 @@ "name": "team_member_team_id_team_id_fk", "tableFrom": "team_member", "tableTo": "team", - "columnsFrom": [ - "team_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["team_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3477,12 +3214,8 @@ "name": "team_member_user_id_user_id_fk", "tableFrom": "team_member", "tableTo": "user", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["user_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3594,12 +3327,8 @@ "name": "job_job_agent_id_job_agent_id_fk", "tableFrom": "job", "tableTo": "job_agent", - "columnsFrom": [ - "job_agent_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_agent_id"], + "columnsTo": ["id"], "onDelete": "set null", "onUpdate": "no action" } @@ -3665,12 +3394,8 @@ "name": "job_metadata_job_id_job_id_fk", "tableFrom": "job_metadata", "tableTo": "job", - "columnsFrom": [ - "job_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3743,12 +3468,8 @@ "name": "job_variable_job_id_job_id_fk", "tableFrom": "job_variable", "tableTo": "job", - "columnsFrom": [ - "job_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["job_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -3799,9 +3520,7 @@ "workspace_slug_unique": { "name": "workspace_slug_unique", "nullsNotDistinct": false, - "columns": [ - "slug" - ] + "columns": ["slug"] } } }, @@ -3889,12 +3608,8 @@ "name": "workspace_email_domain_matching_workspace_id_workspace_id_fk", "tableFrom": "workspace_email_domain_matching", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -3902,12 +3617,8 @@ "name": "workspace_email_domain_matching_role_id_role_id_fk", "tableFrom": "workspace_email_domain_matching", "tableTo": "role", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["role_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3951,12 +3662,8 @@ "name": "variable_set_system_id_system_id_fk", "tableFrom": "variable_set", "tableTo": "system", - "columnsFrom": [ - "system_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["system_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -3994,12 +3701,8 @@ "name": "variable_set_environment_variable_set_id_variable_set_id_fk", "tableFrom": "variable_set_environment", "tableTo": "variable_set", - "columnsFrom": [ - "variable_set_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["variable_set_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -4007,12 +3710,8 @@ "name": "variable_set_environment_environment_id_environment_id_fk", "tableFrom": "variable_set_environment", "tableTo": "environment", - "columnsFrom": [ - "environment_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["environment_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4078,12 +3777,8 @@ "name": "variable_set_value_variable_set_id_variable_set_id_fk", "tableFrom": "variable_set_value", "tableTo": "variable_set", - "columnsFrom": [ - "variable_set_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["variable_set_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4140,12 +3835,8 @@ "name": "workspace_invite_token_role_id_role_id_fk", "tableFrom": "workspace_invite_token", "tableTo": "role", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["role_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -4153,12 +3844,8 @@ "name": "workspace_invite_token_workspace_id_workspace_id_fk", "tableFrom": "workspace_invite_token", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" }, @@ -4166,12 +3853,8 @@ "name": "workspace_invite_token_created_by_user_id_fk", "tableFrom": "workspace_invite_token", "tableTo": "user", - "columnsFrom": [ - "created_by" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["created_by"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4181,9 +3864,7 @@ "workspace_invite_token_token_unique": { "name": "workspace_invite_token_token_unique", "nullsNotDistinct": false, - "columns": [ - "token" - ] + "columns": ["token"] } } }, @@ -4236,12 +3917,8 @@ "name": "resource_metadata_group_workspace_id_workspace_id_fk", "tableFrom": "resource_metadata_group", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4327,12 +4004,8 @@ "name": "runbook_variable_runbook_id_runbook_id_fk", "tableFrom": "runbook_variable", "tableTo": "runbook", - "columnsFrom": [ - "runbook_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["runbook_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4430,12 +4103,8 @@ "name": "entity_role_role_id_role_id_fk", "tableFrom": "entity_role", "tableTo": "role", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["role_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4479,12 +4148,8 @@ "name": "role_workspace_id_workspace_id_fk", "tableFrom": "role", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4544,12 +4209,8 @@ "name": "role_permission_role_id_role_id_fk", "tableFrom": "role_permission", "tableTo": "role", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["role_id"], + "columnsTo": ["id"], "onDelete": "cascade", "onUpdate": "no action" } @@ -4622,12 +4283,8 @@ "name": "job_agent_workspace_id_workspace_id_fk", "tableFrom": "job_agent", "tableTo": "workspace", - "columnsFrom": [ - "workspace_id" - ], - "columnsTo": [ - "id" - ], + "columnsFrom": ["workspace_id"], + "columnsTo": ["id"], "onDelete": "no action", "onUpdate": "no action" } @@ -4640,54 +4297,32 @@ "public.environment_policy_approval_requirement": { "name": "environment_policy_approval_requirement", "schema": "public", - "values": [ - "manual", - "automatic" - ] + "values": ["manual", "automatic"] }, "public.approval_status_type": { "name": "approval_status_type", "schema": "public", - "values": [ - "pending", - "approved", - "rejected" - ] + "values": ["pending", "approved", "rejected"] }, "public.environment_policy_deployment_success_type": { "name": "environment_policy_deployment_success_type", "schema": "public", - "values": [ - "all", - "some", - "optional" - ] + "values": ["all", "some", "optional"] }, "public.recurrence_type": { "name": "recurrence_type", "schema": "public", - "values": [ - "hourly", - "daily", - "weekly", - "monthly" - ] + "values": ["hourly", "daily", "weekly", "monthly"] }, "public.release_sequencing_type": { "name": "release_sequencing_type", "schema": "public", - "values": [ - "wait", - "cancel" - ] + "values": ["wait", "cancel"] }, "public.resource_relationship_type": { "name": "resource_relationship_type", "schema": "public", - "values": [ - "associated_with", - "depends_on" - ] + "values": ["associated_with", "depends_on"] }, "public.release_job_trigger_type": { "name": "release_job_trigger_type", @@ -4733,10 +4368,7 @@ "public.entity_type": { "name": "entity_type", "schema": "public", - "values": [ - "user", - "team" - ] + "values": ["user", "team"] }, "public.scope_type": { "name": "scope_type", @@ -4768,4 +4400,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index b784a410..7e29693e 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -346,4 +346,4 @@ "breakpoints": true } ] -} \ No newline at end of file +} From f992cba4ce60526050d61648d654fabfbd3b5a14 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Sun, 22 Dec 2024 15:10:58 -0500 Subject: [PATCH 4/6] safer duration str check --- .../environment-policy-drawer/RolloutAndTiming.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx index d7745f59..a89e7211 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/RolloutAndTiming.tsx @@ -34,7 +34,14 @@ import { toast } from "@ctrlplane/ui/toast"; import { api } from "~/trpc/react"; import { useInvalidatePolicy } from "./useInvalidatePolicy"; -const isValidDuration = (str: string) => !isNaN(ms(str)); +const isValidDuration = (str: string) => { + try { + const duration = ms(str); + return !Number.isNaN(duration) && duration >= 0; + } catch { + return false; + } +}; const schema = z.object({ releaseWindows: z.array( From 67e356d0d405545de1e79648ae364ab75fc15b27 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Sun, 22 Dec 2024 19:49:20 -0500 Subject: [PATCH 5/6] add timer UI --- .../releases/[versionId]/FlowDiagram.tsx | 2 +- .../[versionId]/ReleaseSequencingNode.tsx | 77 ++++++++++++++++++- packages/api/src/router/release.ts | 66 +++++++++++++++- 3 files changed, 140 insertions(+), 5 deletions(-) diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowDiagram.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowDiagram.tsx index a7f66ed8..270a23e1 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowDiagram.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/FlowDiagram.tsx @@ -79,7 +79,7 @@ export const FlowDiagram: React.FC<{ releaseVersion: release.version, deploymentId: release.deploymentId, environmentId: env.id, - policyType: policy?.releaseSequencing, + policy, label: `${env.name} - release sequencing`, }, }; diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ReleaseSequencingNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ReleaseSequencingNode.tsx index c1d5aa3c..b7b35b56 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ReleaseSequencingNode.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ReleaseSequencingNode.tsx @@ -1,3 +1,4 @@ +import type * as SCHEMA from "@ctrlplane/db/schema"; import type { EnvironmentCondition, JobCondition, @@ -6,8 +7,11 @@ import type { } from "@ctrlplane/validators/jobs"; import type { ReleaseCondition } from "@ctrlplane/validators/releases"; import type { NodeProps } from "reactflow"; +import { useEffect, useState } from "react"; import { IconCheck, IconLoader2, IconMinus, IconX } from "@tabler/icons-react"; +import { differenceInMilliseconds } from "date-fns"; import _ from "lodash"; +import prettyMilliseconds from "pretty-ms"; import { Handle, Position } from "reactflow"; import colors from "tailwindcss/colors"; @@ -26,7 +30,7 @@ import { api } from "~/trpc/react"; type ReleaseSequencingNodeProps = NodeProps<{ workspaceId: string; - policyType?: "cancel" | "wait"; + policy?: SCHEMA.EnvironmentPolicy; releaseId: string; releaseVersion: string; deploymentId: string; @@ -52,8 +56,8 @@ const Waiting: React.FC = () => ( ); const Loading: React.FC = () => ( -
- +
+
); @@ -243,6 +247,72 @@ const ReleaseChannelCheck: React.FC = ({ ); }; +const MinReleaseIntervalCheck: React.FC = ({ + policy, + deploymentId, + environmentId, +}) => { + const [timeLeft, setTimeLeft] = useState(null); + + const { data: latestRelease, isLoading } = + api.release.latest.completed.useQuery( + { deploymentId, environmentId }, + { enabled: policy != null }, + ); + + useEffect(() => { + if (!latestRelease || !policy?.minimumReleaseInterval) return; + + const calculateTimeLeft = () => { + const timePassed = differenceInMilliseconds( + new Date(), + latestRelease.createdAt, + ); + return Math.max(0, policy.minimumReleaseInterval - timePassed); + }; + + setTimeLeft(calculateTimeLeft()); + + const interval = setInterval(() => { + const remaining = calculateTimeLeft(); + setTimeLeft(remaining); + + if (remaining <= 0) clearInterval(interval); + }, 1000); + + return () => clearInterval(interval); + }, [latestRelease, policy?.minimumReleaseInterval]); + + if (policy == null) return null; + const { minimumReleaseInterval } = policy; + if (minimumReleaseInterval === 0) return null; + if (isLoading) + return ( +
+ +
+ ); + + if (latestRelease == null || timeLeft === 0) { + return ( +
+ + Minimum release interval passed +
+ ); + } + + return ( +
+ + + Waiting {prettyMilliseconds(timeLeft ?? 0, { compact: true })} till next + release + +
+ ); +}; + export const ReleaseSequencingNode: React.FC = ({ data, }) => { @@ -255,6 +325,7 @@ export const ReleaseSequencingNode: React.FC = ({ > +
+ canUser.perform(Permission.ReleaseGet).on({ + type: "deployment", + id: input.deploymentId, + }), + }) + .query(({ input: { deploymentId, environmentId } }) => + db + .select() + .from(release) + .innerJoin(environment, eq(environment.id, environmentId)) + .where( + and( + eq(release.deploymentId, deploymentId), + exists( + db + .select() + .from(releaseJobTrigger) + .where( + and( + eq(releaseJobTrigger.releaseId, release.id), + eq(releaseJobTrigger.environmentId, environmentId), + ), + ) + .limit(1), + ), + notExists( + db + .select() + .from(releaseJobTrigger) + .innerJoin(job, eq(releaseJobTrigger.jobId, job.id)) + .where( + and( + eq(releaseJobTrigger.releaseId, release.id), + eq(releaseJobTrigger.environmentId, environmentId), + inArray(job.status, [...activeStatus, JobStatus.Pending]), + ), + ) + .limit(1), + ), + ), + ) + .orderBy(desc(release.createdAt)) + .limit(1) + .then(takeFirstOrNull) + .then((r) => r?.release ?? null), + ), + }), + metadataKeys: releaseMetadataKeysRouter, }); From f5634af9d5c036f00c7caa371dffbda231a3b643 Mon Sep 17 00:00:00 2001 From: Aditya Choudhari Date: Sun, 22 Dec 2024 20:11:44 -0500 Subject: [PATCH 6/6] open policy drawer --- .../[versionId]/ReleaseSequencingNode.tsx | 92 ++++++++++++------- 1 file changed, 59 insertions(+), 33 deletions(-) diff --git a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ReleaseSequencingNode.tsx b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ReleaseSequencingNode.tsx index b7b35b56..39359059 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ReleaseSequencingNode.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(app)/systems/[systemSlug]/deployments/[deploymentSlug]/releases/[versionId]/ReleaseSequencingNode.tsx @@ -25,7 +25,9 @@ import { import { JobFilterType, JobStatus } from "@ctrlplane/validators/jobs"; import { ReleaseFilterType } from "@ctrlplane/validators/releases"; +import { EnvironmentPolicyDrawerTab } from "~/app/[workspaceSlug]/(app)/_components/environment-policy-drawer/EnvironmentPolicyDrawer"; import { useReleaseChannelDrawer } from "~/app/[workspaceSlug]/(app)/_components/release-channel-drawer/useReleaseChannelDrawer"; +import { useQueryParams } from "~/app/[workspaceSlug]/(app)/_components/useQueryParams"; import { api } from "~/trpc/react"; type ReleaseSequencingNodeProps = NodeProps<{ @@ -253,6 +255,7 @@ const MinReleaseIntervalCheck: React.FC = ({ environmentId, }) => { const [timeLeft, setTimeLeft] = useState(null); + const { setParams } = useQueryParams(); const { data: latestRelease, isLoading } = api.release.latest.completed.useQuery( @@ -293,21 +296,46 @@ const MinReleaseIntervalCheck: React.FC = ({
); - if (latestRelease == null || timeLeft === 0) { + if (latestRelease == null || timeLeft === 0) return (
- Minimum release interval passed + + Minimum + + passed +
); - } return (
- - Waiting {prettyMilliseconds(timeLeft ?? 0, { compact: true })} till next - release + + + till next release
); @@ -315,30 +343,28 @@ const MinReleaseIntervalCheck: React.FC = ({ export const ReleaseSequencingNode: React.FC = ({ data, -}) => { - return ( - <> -
- - - -
- - - - ); -}; +}) => ( + <> +
+ + + +
+ + + +);