Skip to content

Commit

Permalink
feat: add more options to create actor form
Browse files Browse the repository at this point in the history
  • Loading branch information
jog1t committed Jan 27, 2025
1 parent 057e98b commit cc63118
Show file tree
Hide file tree
Showing 8 changed files with 205 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ export default function CreateActorDialog({
projectNameId,
environmentNameId,
buildId: values.buildId,
region: values.regionId,
parameters: values.parameters,
});
}}
defaultValues={{ buildId: "" }}
defaultValues={{ buildId: "", regionId: "" }}
>
<DialogHeader>
<DialogTitle>Create Actor</DialogTitle>
Expand All @@ -47,6 +49,15 @@ export default function CreateActorDialog({
projectNameId={projectNameId}
environmentNameId={environmentNameId}
/>
<ActorCreateForm.Region
projectNameId={projectNameId}
environmentNameId={environmentNameId}
/>
<ActorCreateForm.Tags
projectNameId={projectNameId}
environmentNameId={environmentNameId}
/>
<ActorCreateForm.Parameters />
</Flex>
<DialogFooter>
<ActorCreateForm.Submit type="submit">
Expand Down
54 changes: 54 additions & 0 deletions frontend/apps/hub/src/domains/project/components/region-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Combobox } from "@rivet-gg/components";
import { useSuspenseQuery } from "@tanstack/react-query";
import { actorRegionsQueryOptions } from "../queries";
import { ActorRegion } from "./actors/actor-region";

interface RegionSelectProps {
projectNameId: string;
environmentNameId: string;
onValueChange: (value: string) => void;
value: string;
}

export function RegionSelect({
projectNameId,
environmentNameId,
onValueChange,
value,
}: RegionSelectProps) {
const { data } = useSuspenseQuery(
actorRegionsQueryOptions({
projectNameId,
environmentNameId,
}),
);

const regions = data.map((region) => {
return {
label: (
<ActorRegion
projectNameId={projectNameId}
environmentNameId={environmentNameId}
regionId={region.id}
showLabel
/>
),
value: region.id,
region,
};
});

return (
<Combobox
placeholder="Choose a region..."
options={regions}
value={value}
onValueChange={onValueChange}
filter={(option, search) =>
option.region.id.includes(search) ||
option.region.name.includes(search)
}
className="w-full"
/>
);
}
121 changes: 120 additions & 1 deletion frontend/apps/hub/src/domains/project/forms/actor-create-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,37 @@ import {
FormItem,
FormLabel,
FormMessage,
Label,
createSchemaForm,
} from "@rivet-gg/components";
import { JsonCode } from "@rivet-gg/components/code-mirror";
import { type UseFormReturn, useFormContext } from "react-hook-form";
import z from "zod";
import { BuildSelect } from "../components/build-select";
import { RegionSelect } from "../components/region-select";

import {
Tags as TagsInput,
formSchema as tagsFormSchema,
} from "@/domains/project/forms/build-tags-form";
import { useSuspenseQuery } from "@tanstack/react-query";
import { useState } from "react";
import { actorBuildsQueryOptions } from "../queries";

const jsonValid = z.custom<string>((value) => {
try {
JSON.parse(value);
return true;
} catch {
return false;
}
});

export const formSchema = z.object({
buildId: z.string(),
buildId: z.string().nonempty(),
regionId: z.string().nonempty(),
parameters: jsonValid.optional(),
tags: tagsFormSchema.shape.tags,
});

export type FormValues = z.infer<typeof formSchema>;
Expand Down Expand Up @@ -49,3 +72,99 @@ export const Build = ({
/>
);
};

export const Region = ({
projectNameId,
environmentNameId,
}: { projectNameId: string; environmentNameId: string }) => {
const { control } = useFormContext<FormValues>();
return (
<FormField
control={control}
name="regionId"
render={({ field }) => (
<FormItem>
<FormLabel>Region</FormLabel>
<FormControl>
<RegionSelect
projectNameId={projectNameId}
environmentNameId={environmentNameId}
onValueChange={field.onChange}
value={field.value}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
};

export const Parameters = () => {
const { control } = useFormContext<FormValues>();
return (
<FormField
control={control}
name="parameters"
render={({ field }) => (
<FormItem>
<FormLabel>Parameters</FormLabel>
<FormControl>
<JsonCode {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
);
};

export const Tags = ({
projectNameId,
environmentNameId,
}: { projectNameId: string; environmentNameId: string }) => {
const { data: builds } = useSuspenseQuery(
actorBuildsQueryOptions({ projectNameId, environmentNameId }),
);

const [tagKeys, setTagKeys] = useState(() =>
Array.from(
new Set(
builds?.flatMap((build) => Object.keys(build.tags)),
).values(),
).map((key) => ({
label: key,
value: key,
})),
);

const [tagValues, setTagValues] = useState(() =>
Array.from(
new Set(
builds?.flatMap((build) => Object.values(build.tags)),
).values(),
).map((key) => ({ label: key, value: key })),
);

return (
<div className="space-y-2">
<Label>Tags</Label>
<TagsInput
keys={tagKeys}
values={tagValues}
onCreateKeyOption={(option) =>
setTagKeys((opts) => [
...opts,
{ label: option, value: option },
])
}
onCreateValueOption={(option) =>
setTagValues((opts) => [
...opts,
{ label: option, value: option },
])
}
/>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
FormItem,
FormLabel,
FormMessage,
type Option,
type ComboboxOption as Option,
Text,
createSchemaForm,
} from "@rivet-gg/components";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,14 @@ export function useCreateActorFromSdkMutation({
projectNameId,
environmentNameId,
buildId,
region,
parameters,
}: {
projectNameId: string;
environmentNameId: string;
buildId: string;
region: string;
parameters: unknown;
}) => {
const managerUrl = await queryClient.fetchQuery(
actorManagerUrlQueryOptions({
Expand All @@ -158,7 +162,8 @@ export function useCreateActorFromSdkMutation({
const cl = new Client(managerUrl);

await cl.create({
create: { tags: { name: build.tags.name || build.id } },
parameters,
create: { tags: { name: build.tags.name || build.id }, region },
});
},
onSuccess: async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ export const actorBuildsQueryOptions = ({
"actor-builds",
tags,
] as const,
refetchInterval: 5000,
refetchInterval: 2000,
queryFn: ({
queryKey: [
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function ProjectActorsRoute() {
projectNameId,
environmentNameId,
tags: tagsRecord,
includeDestroyed: showDestroyed,
includeDestroyed: showDestroyed ?? true,
}),
);

Expand Down
36 changes: 10 additions & 26 deletions frontend/packages/components/src/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ const buttonVariants = cva(
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
xs: "h-5 rounded-md px-2 text-xs",
sm: "h-7 rounded-md px-2 text-xs [&_svg]:size-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
"icon-sm": "h-7 w-7 text-xs [&_svg]:size-3",
"icon-xs": "h-5 w-5 text-xs [&_svg]:size-2",
default: "h-10 px-4 py-2 gap-1.5",
xs: "h-5 rounded-md px-2 text-xs gap-0.5",
sm: "h-7 rounded-md px-2 text-xs [&_svg]:size-3 gap-1.5",
lg: "h-11 rounded-md px-8 gap-2",
icon: "h-10 w-10 gap-1.5",
"icon-sm": "h-7 w-7 text-xs [&_svg]:size-3 gap-1.5",
"icon-xs": "h-5 w-5 text-xs [&_svg]:size-2 gap-0.5",
},
},
defaultVariants: {
Expand Down Expand Up @@ -88,31 +88,15 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
{isLoading ? (
<Icon
icon={faSpinnerThird}
className={cn(
"h-4 w-4 animate-spin",
!size?.includes("icon") && "mr-2",
)}
className={cn("h-4 w-4 animate-spin")}
/>
) : startIcon ? (
React.cloneElement(startIcon, {
className: cn(
"mr-2",
{
"mr-1.5": size === "sm",
},
startIcon.props.className,
),
})
React.cloneElement(startIcon, startIcon.props)
) : null}
{!size?.includes("icon") && isLoading ? null : (
<Slottable>{children}</Slottable>
)}
{endIcon
? React.cloneElement(endIcon, {
...endIcon.props,
className: cn("ml-2", endIcon.props.className),
})
: null}
{endIcon ? React.cloneElement(endIcon, endIcon.props) : null}
</C>
);
},
Expand Down

0 comments on commit cc63118

Please sign in to comment.