From bf077e5d51e86415a69aed4389a342197986d445 Mon Sep 17 00:00:00 2001 From: Zachary Blasczyk <77289967+zacharyblasczyk@users.noreply.github.com> Date: Thu, 29 Aug 2024 00:03:17 -0500 Subject: [PATCH] feat: Update and Delete deployments with cascade, also lint fix (#3) Signed-off-by: Zachary Blasczyk <77289967+zacharyblasczyk@users.noreply.github.com> --- agents/github-app/eslint.config.js | 2 +- apps/docs/package.json | 2 +- apps/event-worker/eslint.config.js | 2 +- apps/event-worker/src/index.ts | 4 +- apps/event-worker/src/redis.ts | 2 +- apps/event-worker/src/target-scan/gke.ts | 7 +- apps/event-worker/src/target-scan/google.ts | 2 +- apps/event-worker/src/target-scan/index.ts | 3 +- apps/event-worker/src/target-scan/upsert.ts | 7 +- apps/event-worker/src/utils.ts | 2 - apps/job-policy-checker/eslint.config.js | 2 +- .../add/TargetProviderSelectCard.tsx | 4 +- .../target-providers/integrations/page.tsx | 4 +- .../targets/[targetId]/variables/page.tsx | 2 +- .../(targets)/targets/page.tsx | 4 +- .../_components/DeleteDeployment.tsx | 63 + .../_components/DeploymentOptionsDropdown.tsx | 61 + .../_components/EditDeploymentDialog.tsx | 139 + .../dashboards/widgets/TextHeading.tsx | 2 +- .../(integration)/github/GithubConfigFile.tsx | 1 + .../deployments/TableDeployments.tsx | 13 +- .../[deploymentSlug]/JobAgentMissingAlert.tsx | 4 +- .../environments/FlowNodeTypes.tsx | 2 +- .../src/app/[workspaceSlug]/systems/page.tsx | 2 +- .../app/api/github/config/handle-configs.ts | 4 +- .../src/app/api/github/config/route.ts | 2 +- .../app/join/[workspaceInviteToken]/page.tsx | 4 +- package.json | 2 +- packages/api/src/router/deployment.ts | 14 + packages/api/src/router/job.ts | 7 +- packages/auth/eslint.config.js | 2 +- packages/auth/package.json | 2 +- .../drizzle/0001_tranquil_james_howlett.sql | 11 + packages/db/drizzle/meta/0001_snapshot.json | 2508 ++++++++ packages/db/drizzle/meta/_journal.json | 7 + packages/db/package.json | 1 + packages/db/src/schema/release.ts | 2 +- packages/job-dispatch/src/queue/index.ts | 4 +- packages/node-sdk/eslint.config.js | 8 +- packages/node-sdk/tsconfig.json | 2 +- packages/node-sdk/tsup.config.js | 13 +- packages/ui/src/button.tsx | 2 +- packages/ui/src/copy-button.tsx | 25 + .../ui/src/date-time-picker/date-field.tsx | 2 +- packages/ui/src/dialog.tsx | 2 +- packages/ui/src/navigation-menu.tsx | 2 +- pnpm-lock.yaml | 5603 ++++++++--------- pnpm-workspace.yaml | 1 + tooling/eslint/package.json | 10 +- 49 files changed, 5438 insertions(+), 3138 deletions(-) create mode 100644 apps/webservice/src/app/[workspaceSlug]/_components/DeleteDeployment.tsx create mode 100644 apps/webservice/src/app/[workspaceSlug]/_components/DeploymentOptionsDropdown.tsx create mode 100644 apps/webservice/src/app/[workspaceSlug]/_components/EditDeploymentDialog.tsx create mode 100644 packages/db/drizzle/0001_tranquil_james_howlett.sql create mode 100644 packages/db/drizzle/meta/0001_snapshot.json create mode 100644 packages/ui/src/copy-button.tsx diff --git a/agents/github-app/eslint.config.js b/agents/github-app/eslint.config.js index dba3e699..7e2fed8a 100644 --- a/agents/github-app/eslint.config.js +++ b/agents/github-app/eslint.config.js @@ -3,7 +3,7 @@ import baseConfig from "@ctrlplane/eslint-config/base"; /** @type {import('typescript-eslint').Config} */ export default [ { - ignores: [".nitro/**", ".output/**"], + ignores: [".nitro/**", ".output/**", "dist/**", "node_modules/**"], }, ...baseConfig, ]; diff --git a/apps/docs/package.json b/apps/docs/package.json index 8023e43c..d69cd785 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -32,6 +32,6 @@ "prettier": "catalog:", "tailwindcss": "catalog:", "typescript": "catalog:", - "typescript-eslint": "^7.8.0" + "typescript-eslint": "catalog:" } } diff --git a/apps/event-worker/eslint.config.js b/apps/event-worker/eslint.config.js index 0a6674b9..3a9fadb9 100644 --- a/apps/event-worker/eslint.config.js +++ b/apps/event-worker/eslint.config.js @@ -5,6 +5,6 @@ export default [ { ignores: [".nitro/**", ".output/**"], }, - requireJsSuffix, + ...requireJsSuffix, ...baseConfig, ]; diff --git a/apps/event-worker/src/index.ts b/apps/event-worker/src/index.ts index ff3ff819..f130e464 100644 --- a/apps/event-worker/src/index.ts +++ b/apps/event-worker/src/index.ts @@ -1,7 +1,7 @@ import { logger } from "@ctrlplane/logger"; -import { redis } from "./redis"; -import { createTargetScanWorker } from "./target-scan"; +import { redis } from "./redis.js"; +import { createTargetScanWorker } from "./target-scan/index.js"; const targetScanWorker = createTargetScanWorker(); diff --git a/apps/event-worker/src/redis.ts b/apps/event-worker/src/redis.ts index d09f2b40..1e9c3849 100644 --- a/apps/event-worker/src/redis.ts +++ b/apps/event-worker/src/redis.ts @@ -1,6 +1,6 @@ import IORedis from "ioredis"; -import { env } from "./config"; +import { env } from "./config.js"; export const redis = new IORedis(env.REDIS_URL, { maxRetriesPerRequest: null, diff --git a/apps/event-worker/src/target-scan/gke.ts b/apps/event-worker/src/target-scan/gke.ts index ee48f297..193204b6 100644 --- a/apps/event-worker/src/target-scan/gke.ts +++ b/apps/event-worker/src/target-scan/gke.ts @@ -1,17 +1,16 @@ +import type { TargetProviderGoogle, Workspace } from "@ctrlplane/db/schema"; import { CoreV1Api } from "@kubernetes/client-node"; -import { Job } from "bullmq"; import _ from "lodash"; -import { TargetProviderGoogle, Workspace } from "@ctrlplane/db/schema"; import { logger } from "@ctrlplane/logger"; +import type { UpsertTarget } from "./upsert.js"; import { clusterToTarget, connectToCluster, getClusters, getGoogleClusterClient, } from "./google.js"; -import { UpsertTarget } from "./upsert.js"; const log = logger.child({ label: "target-scan/gke" }); @@ -52,7 +51,7 @@ export const getGkeTargets = async ( ); const kubernetesNamespaceTargets = ( await Promise.all( - clusters.flatMap(({ project, clusters }, idx) => { + clusters.flatMap(({ project, clusters }) => { return clusters.flatMap(async (cluster) => { if (cluster.name == null || cluster.location == null) return []; diff --git a/apps/event-worker/src/target-scan/google.ts b/apps/event-worker/src/target-scan/google.ts index fcd40f36..a4c79367 100644 --- a/apps/event-worker/src/target-scan/google.ts +++ b/apps/event-worker/src/target-scan/google.ts @@ -1,6 +1,6 @@ import type { ClusterManagerClient } from "@google-cloud/container"; +import type { google } from "@google-cloud/container/build/protos/protos.js"; import Container from "@google-cloud/container"; -import { google } from "@google-cloud/container/build/protos/protos.js"; import { KubeConfig } from "@kubernetes/client-node"; import { GoogleAuth } from "google-auth-library"; import { SemVer } from "semver"; diff --git a/apps/event-worker/src/target-scan/index.ts b/apps/event-worker/src/target-scan/index.ts index 86ab4a58..bd64cf93 100644 --- a/apps/event-worker/src/target-scan/index.ts +++ b/apps/event-worker/src/target-scan/index.ts @@ -1,5 +1,6 @@ import type { TargetScanEvent } from "@ctrlplane/validators/events"; -import { Job, Queue, Worker } from "bullmq"; +import type { Job } from "bullmq"; +import { Queue, Worker } from "bullmq"; import { eq, takeFirstOrNull } from "@ctrlplane/db"; import { db } from "@ctrlplane/db/client"; diff --git a/apps/event-worker/src/target-scan/upsert.ts b/apps/event-worker/src/target-scan/upsert.ts index 3cf12964..8e128162 100644 --- a/apps/event-worker/src/target-scan/upsert.ts +++ b/apps/event-worker/src/target-scan/upsert.ts @@ -1,5 +1,8 @@ -import { buildConflictUpdateColumns, sql, Tx } from "@ctrlplane/db"; -import { Target, target } from "@ctrlplane/db/schema"; +import type { Tx } from "@ctrlplane/db"; +import type { Target } from "@ctrlplane/db/schema"; + +import { buildConflictUpdateColumns, sql } from "@ctrlplane/db"; +import { target } from "@ctrlplane/db/schema"; export type UpsertTarget = Pick< Target, diff --git a/apps/event-worker/src/utils.ts b/apps/event-worker/src/utils.ts index a85fb555..dc99eda3 100644 --- a/apps/event-worker/src/utils.ts +++ b/apps/event-worker/src/utils.ts @@ -1,5 +1,3 @@ -import { Target } from "@ctrlplane/db/schema"; - export function omitNullUndefined(obj: object) { return Object.entries(obj).reduce>( (acc, [key, value]) => { diff --git a/apps/job-policy-checker/eslint.config.js b/apps/job-policy-checker/eslint.config.js index dba3e699..7e2fed8a 100644 --- a/apps/job-policy-checker/eslint.config.js +++ b/apps/job-policy-checker/eslint.config.js @@ -3,7 +3,7 @@ import baseConfig from "@ctrlplane/eslint-config/base"; /** @type {import('typescript-eslint').Config} */ export default [ { - ignores: [".nitro/**", ".output/**"], + ignores: [".nitro/**", ".output/**", "dist/**", "node_modules/**"], }, ...baseConfig, ]; diff --git a/apps/webservice/src/app/[workspaceSlug]/(targets)/target-providers/add/TargetProviderSelectCard.tsx b/apps/webservice/src/app/[workspaceSlug]/(targets)/target-providers/add/TargetProviderSelectCard.tsx index f4e31506..434d5cd5 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(targets)/target-providers/add/TargetProviderSelectCard.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(targets)/target-providers/add/TargetProviderSelectCard.tsx @@ -1,4 +1,4 @@ -import { SiAmazonaws, SiGooglecloud, SiMicrosoftazure } from "react-icons/si"; +import { SiAmazon, SiGooglecloud, SiMicrosoftazure } from "react-icons/si"; import { TbApi, TbCaretDownFilled } from "react-icons/tb"; import { Button } from "@ctrlplane/ui/button"; @@ -41,7 +41,7 @@ export const TargetProviderSelectCard: React.FC<{ diff --git a/apps/webservice/src/app/[workspaceSlug]/(targets)/target-providers/integrations/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(targets)/target-providers/integrations/page.tsx index 6904f6c5..6efff10d 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(targets)/target-providers/integrations/page.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(targets)/target-providers/integrations/page.tsx @@ -1,6 +1,6 @@ import React from "react"; import { - SiAmazonaws, + SiAmazon, SiGooglecloud, SiKubernetes, SiMicrosoftazure, @@ -73,7 +73,7 @@ const TargetProviders: React.FC = () => { - +
Amazon

diff --git a/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/[targetId]/variables/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/[targetId]/variables/page.tsx index d5c18ce0..a55cf40a 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/[targetId]/variables/page.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/[targetId]/variables/page.tsx @@ -222,7 +222,7 @@ export default function VariablePage({ - + Keys Value diff --git a/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/page.tsx b/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/page.tsx index 165ac78f..c461564f 100644 --- a/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/page.tsx +++ b/apps/webservice/src/app/[workspaceSlug]/(targets)/targets/page.tsx @@ -324,7 +324,7 @@ export default function TargetsPage({ {targets.data?.total != null && ( -
+
Total: General - + Deployments diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/DeleteDeployment.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/DeleteDeployment.tsx new file mode 100644 index 00000000..cd908d1c --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/_components/DeleteDeployment.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useRouter } from "next/navigation"; + +import { + AlertDialog, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@ctrlplane/ui/alert-dialog"; +import { Button } from "@ctrlplane/ui/button"; + +import { api } from "~/trpc/react"; + +type DeleteDeploymentProps = { + id: string; + name: string; + isOpen: boolean; + setIsOpen: (isOpen: boolean) => void; +}; + +export const DeleteDeploymentDialog: React.FC = ({ + id, + name, + isOpen, + setIsOpen, +}) => { + const router = useRouter(); + const deleteDeployment = api.deployment.delete.useMutation(); + + const onDelete = async () => { + await deleteDeployment.mutateAsync(id); + router.refresh(); + setIsOpen(false); + }; + + return ( + + + + Delete Deployment + + + Are you sure you want to delete the{" "} + + {name} + {" "} + deployment? This action cannot be undone. + + + + + + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/DeploymentOptionsDropdown.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/DeploymentOptionsDropdown.tsx new file mode 100644 index 00000000..2a53c1aa --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/_components/DeploymentOptionsDropdown.tsx @@ -0,0 +1,61 @@ +"use client"; + +import React, { useState } from "react"; +import { TbDotsVertical, TbEdit, TbTrash } from "react-icons/tb"; + +import { Button } from "@ctrlplane/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@ctrlplane/ui/dropdown-menu"; + +import { DeleteDeploymentDialog } from "./DeleteDeployment"; +import { EditDeploymentDialog } from "./EditDeploymentDialog"; + +export const DeploymentOptionsDropdown: React.FC<{ + id: string; + name: string; + slug: string; + description: string; +}> = (props) => { + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + + return ( + <> + + + + + + + + e.preventDefault()}> + + Edit + + + setDeleteDialogOpen(true)}> + + Delete + + + + + + + + ); +}; diff --git a/apps/webservice/src/app/[workspaceSlug]/_components/EditDeploymentDialog.tsx b/apps/webservice/src/app/[workspaceSlug]/_components/EditDeploymentDialog.tsx new file mode 100644 index 00000000..e89e5020 --- /dev/null +++ b/apps/webservice/src/app/[workspaceSlug]/_components/EditDeploymentDialog.tsx @@ -0,0 +1,139 @@ +"use client"; + +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import { zodResolver } from "@hookform/resolvers/zod"; +import isEqual from "lodash/isEqual"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; + +import { Button } from "@ctrlplane/ui/button"; +import { CopyButton } from "@ctrlplane/ui/copy-button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@ctrlplane/ui/dialog"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, +} from "@ctrlplane/ui/form"; +import { Input } from "@ctrlplane/ui/input"; +import { Textarea } from "@ctrlplane/ui/textarea"; + +import { api } from "~/trpc/react"; + +const deploymentForm = z.object({ + id: z.string().uuid(), + name: z.string().min(3).max(255), + slug: z.string().min(3).max(255), + description: z.string().optional(), +}); + +type DeploymentFormValues = z.infer; + +export const EditDeploymentDialog: React.FC< + DeploymentFormValues & { children?: React.ReactNode } +> = ({ id, name, slug, description, children }) => { + const router = useRouter(); + const update = api.deployment.update.useMutation(); + const [open, setOpen] = useState(false); + + const form = useForm({ + resolver: zodResolver(deploymentForm), + defaultValues: { id, name, slug, description }, + }); + + const onSubmit = form.handleSubmit(async (data) => { + setOpen(false); + const isDataChanged = !isEqual(data, { name, slug, description }); + if (!isDataChanged) return; + + await update.mutateAsync({ id, data }); + + router.refresh(); + }); + + return ( + + {children} + +
+ + + Edit Deployment + + Edit the details of your deployment. + + + ( + + Name + + + + + )} + /> + ( + + Slug + + + + + )} + /> + ( + + Description + +