-
Notifications
You must be signed in to change notification settings - Fork 18
fix: Optimize release metadata keys query #161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| 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"; | ||
|
|
@@ -22,14 +23,30 @@ import { useMatchSorter } from "~/utils/useMatchSorter"; | |
| export const MetadataConditionRender: React.FC< | ||
| ReleaseConditionRenderProps<MetadataCondition> | ||
| > = ({ condition, onChange, className }) => { | ||
| const { workspaceSlug, systemSlug } = useParams(); | ||
| const wSlug = workspaceSlug! as string; | ||
| const sSlug = systemSlug as string | undefined; | ||
| const { workspaceSlug, systemSlug } = useParams<{ | ||
| workspaceSlug: string; | ||
| systemSlug?: string; | ||
| }>(); | ||
|
|
||
| const metadataKeys = api.release.metadataKeys.useQuery({ | ||
| workspaceSlug: wSlug, | ||
| systemSlug: sSlug, | ||
| }); | ||
| 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 }); | ||
|
|
||
|
|
@@ -49,10 +66,13 @@ export const MetadataConditionRender: React.FC< | |
| : onChange({ ...condition, operator, value: condition.value ?? "" }); | ||
|
|
||
| const [open, setOpen] = useState(false); | ||
| const filteredMetadataKeys = useMatchSorter( | ||
| metadataKeys.data ?? [], | ||
| condition.key, | ||
| ); | ||
| const filteredMetadataKeys = useMatchSorter(metadataKeys, condition.key); | ||
|
|
||
| const loadingMetadataKeys = | ||
| workspaceQ.isLoading || | ||
| systemQ.isLoading || | ||
| workspaceMetadataKeys.isLoading || | ||
| systemMetadataKeys.isLoading; | ||
|
Comment on lines
+71
to
+75
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add error handling for query failures Currently, the code does not handle potential errors from the queries. To enhance user experience, consider checking for errors and providing appropriate feedback if any query fails. You can check the if (
workspaceQ.isError ||
systemQ.isError ||
workspaceMetadataKeys.isError ||
systemMetadataKeys.isError
) {
return <div className="p-2 text-sm text-red-500">Error loading metadata keys.</div>;
} |
||
|
|
||
| return ( | ||
| <div className={cn("flex w-full items-center gap-2", className)}> | ||
|
|
@@ -72,20 +92,27 @@ export const MetadataConditionRender: React.FC< | |
| className="scrollbar-thin scrollbar-track-neutral-950 scrollbar-thumb-neutral-800 max-h-[300px] overflow-y-auto p-0 text-sm" | ||
| onOpenAutoFocus={(e) => e.preventDefault()} | ||
| > | ||
| {filteredMetadataKeys.map((k) => ( | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| key={k} | ||
| className="w-full rounded-none text-left" | ||
| onClick={() => { | ||
| setKey(k); | ||
| setOpen(false); | ||
| }} | ||
| > | ||
| <div className="w-full">{k}</div> | ||
| </Button> | ||
| ))} | ||
| {!loadingMetadataKeys && | ||
| filteredMetadataKeys.map((k) => ( | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| key={k} | ||
| className="w-full rounded-none text-left" | ||
| onClick={() => { | ||
| setKey(k); | ||
| setOpen(false); | ||
| }} | ||
| > | ||
| <div className="w-full">{k}</div> | ||
| </Button> | ||
| ))} | ||
| {loadingMetadataKeys && ( | ||
| <div className="flex h-8 items-center gap-1 pl-2 text-xs text-muted-foreground"> | ||
| <IconLoader2 className="h-3 w-3 animate-spin" /> Loading | ||
| keys... | ||
| </div> | ||
| )} | ||
| </PopoverContent> | ||
| </Popover> | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,7 +29,6 @@ import { | |
| releaseMetadata, | ||
| system, | ||
| target, | ||
| workspace, | ||
| } from "@ctrlplane/db/schema"; | ||
| import { | ||
| cancelOldReleaseJobTriggersOnJobDispatch, | ||
|
|
@@ -417,61 +416,44 @@ export const releaseRouter = createTRPCRouter({ | |
| ); | ||
| }), | ||
|
|
||
| metadataKeys: protectedProcedure | ||
| .meta({ | ||
| authorizationCheck: async ({ canUser, input }) => { | ||
| if (input.systemSlug != null) { | ||
| const sys = await db | ||
| .select() | ||
| .from(system) | ||
| .where(eq(system.slug, input.systemSlug)) | ||
| .then(takeFirstOrNull); | ||
| if (sys == null) return false; | ||
|
|
||
| return canUser | ||
| .perform(Permission.ReleaseGet) | ||
| .on({ type: "system", id: sys.id }); | ||
| } | ||
|
|
||
| const ws = await db | ||
| .select() | ||
| .from(workspace) | ||
| .where(eq(workspace.slug, input.workspaceSlug)) | ||
| .then(takeFirstOrNull); | ||
| if (ws == null) return false; | ||
|
|
||
| return canUser | ||
| .perform(Permission.ReleaseGet) | ||
| .on({ type: "workspace", id: ws.id }); | ||
| }, | ||
| }) | ||
| .input( | ||
| z.object({ | ||
| workspaceSlug: z.string(), | ||
| systemSlug: z.string().optional(), | ||
| }), | ||
| ) | ||
| .query(async ({ input }) => { | ||
| const baseQuery = db | ||
| .selectDistinct({ key: releaseMetadata.key }) | ||
| .from(release) | ||
| .innerJoin(releaseMetadata, eq(releaseMetadata.releaseId, release.id)) | ||
| .innerJoin(deployment, eq(release.deploymentId, deployment.id)) | ||
| .innerJoin(system, eq(deployment.systemId, system.id)) | ||
| .innerJoin(workspace, eq(system.workspaceId, workspace.id)); | ||
|
|
||
| if (input.systemSlug != null) | ||
| return baseQuery | ||
| .where( | ||
| and( | ||
| eq(system.slug, input.systemSlug), | ||
| eq(workspace.slug, input.workspaceSlug), | ||
| ), | ||
| ) | ||
| .then((r) => r.map((row) => row.key)); | ||
| metadataKeys: createTRPCRouter({ | ||
| bySystem: protectedProcedure | ||
| .meta({ | ||
| authorizationCheck: ({ canUser, input }) => | ||
| canUser.perform(Permission.ReleaseGet).on({ | ||
| type: "system", | ||
| id: input, | ||
| }), | ||
| }) | ||
| .input(z.string().uuid()) | ||
| .query(async ({ input, ctx }) => | ||
| ctx.db | ||
| .selectDistinct({ key: releaseMetadata.key }) | ||
| .from(release) | ||
| .innerJoin(releaseMetadata, eq(releaseMetadata.releaseId, release.id)) | ||
| .innerJoin(deployment, eq(release.deploymentId, deployment.id)) | ||
| .where(eq(deployment.systemId, input)) | ||
| .then((r) => r.map((row) => row.key)), | ||
| ), | ||
|
Comment on lines
+420
to
+437
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider refactoring duplicated code in The Also applies to: 439-457 |
||
|
|
||
| return baseQuery | ||
| .where(eq(workspace.slug, input.workspaceSlug)) | ||
| .then((r) => r.map((row) => row.key)); | ||
| }), | ||
| byWorkspace: protectedProcedure | ||
| .meta({ | ||
| authorizationCheck: ({ canUser, input }) => | ||
| canUser.perform(Permission.ReleaseGet).on({ | ||
| type: "workspace", | ||
| id: input, | ||
| }), | ||
| }) | ||
| .input(z.string().uuid()) | ||
| .query(async ({ input, ctx }) => | ||
| ctx.db | ||
| .selectDistinct({ key: releaseMetadata.key }) | ||
| .from(release) | ||
| .innerJoin(releaseMetadata, eq(releaseMetadata.releaseId, release.id)) | ||
| .innerJoin(deployment, eq(release.deploymentId, deployment.id)) | ||
| .innerJoin(system, eq(deployment.systemId, system.id)) | ||
| .where(eq(system.workspaceId, input)) | ||
| .then((r) => r.map((row) => row.key)), | ||
| ), | ||
| }), | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid passing empty
systemSlugwhen it isnullWhen
systemSlugisnull, the query is still receivingsystemSlug: "", which may not be necessary and could lead to unintended behavior. It's better to conditionally includesystemSlugonly when it is defined.Consider modifying the query parameters as follows:
📝 Committable suggestion