Skip to content

Commit 0e8b5d3

Browse files
committed
Editing individual env var values is working
1 parent 4e6c4ea commit 0e8b5d3

4 files changed

Lines changed: 208 additions & 141 deletions

File tree

apps/webapp/app/presenters/v3/EnvironmentVariablesPresenter.server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export class EnvironmentVariablesPresenter {
109109
id: environmentVariable.id,
110110
key: environmentVariable.key,
111111
environment: { type: env.type, id: env.id },
112-
value: val.value,
112+
value: isSecret ? "" : val.value,
113113
isSecret,
114114
},
115115
];

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables/route.tsx

Lines changed: 120 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useForm } from "@conform-to/react";
1+
import { conform, useForm } from "@conform-to/react";
22
import { parse } from "@conform-to/zod";
33
import {
44
BookOpenIcon,
@@ -56,6 +56,7 @@ import {
5656
type EnvironmentVariableWithSetValues,
5757
EnvironmentVariablesPresenter,
5858
} from "~/presenters/v3/EnvironmentVariablesPresenter.server";
59+
import { logger } from "~/services/logger.server";
5960
import { requireUserId } from "~/services/session.server";
6061
import { cn } from "~/utils/cn";
6162
import {
@@ -69,6 +70,7 @@ import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/enviro
6970
import {
7071
DeleteEnvironmentVariable,
7172
EditEnvironmentVariable,
73+
EditEnvironmentVariableValue,
7274
} from "~/v3/environmentVariables/repository";
7375

7476
export const meta: MetaFunction = () => {
@@ -105,7 +107,7 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => {
105107
};
106108

107109
const schema = z.discriminatedUnion("action", [
108-
z.object({ action: z.literal("edit"), key: z.string(), ...EditEnvironmentVariable.shape }),
110+
z.object({ action: z.literal("edit"), ...EditEnvironmentVariableValue.shape }),
109111
z.object({ action: z.literal("delete"), key: z.string(), ...DeleteEnvironmentVariable.shape }),
110112
]);
111113

@@ -146,8 +148,11 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
146148

147149
switch (submission.value.action) {
148150
case "edit": {
151+
logger.debug("ENVVARS edit", { submission: submission.value });
149152
const repository = new EnvironmentVariablesRepository(prisma);
150-
const result = await repository.edit(project.id, submission.value);
153+
const result = await repository.editValue(project.id, submission.value);
154+
155+
logger.debug("ENVVARS edit result", { result });
151156

152157
if (!result.success) {
153158
submission.error.key = result.error;
@@ -237,28 +242,37 @@ export default function Page() {
237242
<TableHeaderCell>Key</TableHeaderCell>
238243
<TableHeaderCell>Value</TableHeaderCell>
239244
<TableHeaderCell>Environment</TableHeaderCell>
240-
<TableHeaderCell hiddenLabel>Actions</TableHeaderCell>
245+
<TableHeaderCell hiddenLabel className="pl-24">
246+
Actions
247+
</TableHeaderCell>
241248
</TableRow>
242249
</TableHeader>
243250
<TableBody>
244251
{environmentVariables.length > 0 ? (
245252
environmentVariables.map((variable) => (
246-
<TableRow key={variable.id}>
253+
<TableRow key={`${variable.id}-${variable.environment.id}`}>
247254
<TableCell>
248255
<CopyableText value={variable.key} className="font-mono" />
249256
</TableCell>
250257
<TableCell>
251258
{variable.isSecret ? (
252259
<SimpleTooltip
253-
button={<LockClosedIcon className="size-4 text-charcoal-400" />}
260+
button={
261+
<div className="flex items-center gap-x-1.5">
262+
<LockClosedIcon className="size-4 text-text-dimmed" />
263+
<span className="text-sm text-text-dimmed">Secret</span>
264+
</div>
265+
}
254266
content="This variable is secret and cannot be revealed."
255267
/>
256268
) : (
257269
<ClipboardField
258270
className="-ml-2"
259271
secure={!revealAll}
260272
value={variable.value}
261-
variant={"secondary/small"}
273+
variant={"tertiary/small"}
274+
iconButton
275+
fullWidth={false}
262276
/>
263277
)}
264278
</TableCell>
@@ -268,13 +282,9 @@ export default function Page() {
268282
</TableCell>
269283
<TableCellMenu
270284
isSticky
271-
popoverContent={
285+
hiddenButtons={
272286
<>
273-
{/* <EditEnvironmentVariablePanel
274-
environments={environments}
275-
variable={variable}
276-
revealAll={revealAll}
277-
/> */}
287+
<EditEnvironmentVariablePanel variable={variable} revealAll={revealAll} />
278288
<DeleteEnvironmentVariableButton variable={variable} />
279289
</>
280290
}
@@ -283,7 +293,7 @@ export default function Page() {
283293
))
284294
) : (
285295
<TableRow>
286-
<TableCell colSpan={environments.length + 2}>
296+
<TableCell colSpan={4}>
287297
<div className="flex flex-col items-center justify-center gap-y-4 py-8">
288298
<Header2>You haven't set any environment variables yet.</Header2>
289299
<LinkButton
@@ -314,132 +324,102 @@ export default function Page() {
314324
);
315325
}
316326

317-
// function EditEnvironmentVariablePanel({
318-
// variable,
319-
// environments,
320-
// revealAll,
321-
// }: {
322-
// variable: EnvironmentVariableWithSetValues;
323-
// environments: Pick<RuntimeEnvironment, "id" | "type">[];
324-
// revealAll: boolean;
325-
// }) {
326-
// const [reveal, setReveal] = useState(revealAll);
327-
328-
// const [isOpen, setIsOpen] = useState(false);
329-
// const lastSubmission = useActionData();
330-
// const navigation = useNavigation();
331-
332-
// const hiddenValues = Object.values(variable.values).filter(
333-
// (value) => !environments.map((e) => e.id).includes(value.environment.id)
334-
// );
335-
336-
// const isLoading =
337-
// navigation.state !== "idle" &&
338-
// navigation.formMethod === "post" &&
339-
// navigation.formData?.get("action") === "edit";
340-
341-
// const [form, { id }] = useForm({
342-
// id: "edit-environment-variable",
343-
// // TODO: type this
344-
// lastSubmission: lastSubmission as any,
345-
// onValidate({ formData }) {
346-
// return parse(formData, { schema });
347-
// },
348-
// shouldRevalidate: "onSubmit",
349-
// });
350-
351-
// return (
352-
// <Dialog open={isOpen} onOpenChange={setIsOpen}>
353-
// <DialogTrigger asChild>
354-
// <Button variant="small-menu-item" LeadingIcon={PencilSquareIcon} fullWidth textAlignLeft>
355-
// Edit
356-
// </Button>
357-
// </DialogTrigger>
358-
// <DialogContent>
359-
// <DialogHeader>Edit {variable.key}</DialogHeader>
360-
// <Form method="post" {...form.props}>
361-
// <input type="hidden" name="action" value="edit" />
362-
// <input type="hidden" name="id" value={variable.id} />
363-
// <input type="hidden" name="key" value={variable.key} />
364-
// {hiddenValues.map((value, index) => (
365-
// <Fragment key={index}>
366-
// <input
367-
// type="hidden"
368-
// name={`values[${index}].environmentId`}
369-
// value={value.environment.id}
370-
// />
371-
// <input type="hidden" name={`values[${index}].value`} value={value.value} />
372-
// </Fragment>
373-
// ))}
374-
// <FormError id={id.errorId}>{id.error}</FormError>
375-
// <Fieldset>
376-
// <InputGroup fullWidth className="mb-5 mt-2">
377-
// <Label>Key</Label>
378-
// <InlineCode variant="base" className="pl-1.5">
379-
// {variable.key}
380-
// </InlineCode>
381-
// </InputGroup>
382-
// </Fieldset>
383-
// <Fieldset>
384-
// <InputGroup fullWidth>
385-
// <div className="flex justify-between gap-1">
386-
// <Label>Values</Label>
387-
// <Switch
388-
// variant="small"
389-
// label="Reveal"
390-
// checked={reveal}
391-
// onCheckedChange={(e) => setReveal(e.valueOf())}
392-
// />
393-
// </div>
394-
// <div className="grid grid-cols-[auto_1fr] gap-x-2 gap-y-2">
395-
// {environments.map((environment, index) => {
396-
// const value = variable.values[environment.id]?.value;
397-
// index += hiddenValues.length;
398-
// return (
399-
// <Fragment key={environment.id}>
400-
// <input
401-
// type="hidden"
402-
// name={`values[${index}].environmentId`}
403-
// value={environment.id}
404-
// />
405-
// <label
406-
// className="flex items-center justify-end"
407-
// htmlFor={`values[${index}].value`}
408-
// >
409-
// <EnvironmentCombo environment={environment} className="text-sm" />
410-
// </label>
411-
// <Input
412-
// name={`values[${index}].value`}
413-
// placeholder="Not set"
414-
// defaultValue={value}
415-
// type={reveal ? "text" : "password"}
416-
// />
417-
// </Fragment>
418-
// );
419-
// })}
420-
// </div>
421-
// </InputGroup>
422-
423-
// <FormError>{form.error}</FormError>
424-
425-
// <FormButtons
426-
// confirmButton={
427-
// <Button type="submit" variant="primary/medium" disabled={isLoading}>
428-
// {isLoading ? "Saving…" : "Save"}
429-
// </Button>
430-
// }
431-
// cancelButton={
432-
// <Button onClick={() => setIsOpen(false)} variant="tertiary/medium" type="button">
433-
// Cancel
434-
// </Button>
435-
// }
436-
// />
437-
// </Fieldset>
438-
// </Form>
439-
// </DialogContent>
440-
// </Dialog>
441-
// );
442-
// }
327+
function EditEnvironmentVariablePanel({
328+
variable,
329+
revealAll,
330+
}: {
331+
variable: EnvironmentVariableWithSetValues;
332+
revealAll: boolean;
333+
}) {
334+
const [reveal, setReveal] = useState(revealAll);
335+
336+
const [isOpen, setIsOpen] = useState(false);
337+
const lastSubmission = useActionData();
338+
const navigation = useNavigation();
339+
340+
const isLoading =
341+
navigation.state !== "idle" &&
342+
navigation.formMethod === "post" &&
343+
navigation.formData?.get("action") === "edit";
344+
345+
const [form, { id, environmentId, value }] = useForm({
346+
id: "edit-environment-variable",
347+
// TODO: type this
348+
lastSubmission: lastSubmission as any,
349+
onValidate({ formData }) {
350+
return parse(formData, { schema });
351+
},
352+
shouldRevalidate: "onSubmit",
353+
});
354+
355+
console.log("edit form", { form, variable, id, environmentId, value });
356+
357+
return (
358+
<Dialog open={isOpen} onOpenChange={setIsOpen}>
359+
<DialogTrigger asChild>
360+
<Button variant="small-menu-item" LeadingIcon={PencilSquareIcon} fullWidth textAlignLeft>
361+
Edit
362+
</Button>
363+
</DialogTrigger>
364+
<DialogContent>
365+
<DialogHeader>Edit environment variable</DialogHeader>
366+
<Form method="post" {...form.props}>
367+
<input type="hidden" name="action" value="edit" />
368+
<input {...conform.input(id, { type: "hidden" })} value={variable.id} />
369+
<input
370+
{...conform.input(environmentId, { type: "hidden" })}
371+
value={variable.environment.id}
372+
/>
373+
<FormError id={id.errorId}>{id.error}</FormError>
374+
<FormError id={environmentId.errorId}>{environmentId.error}</FormError>
375+
<Fieldset>
376+
<InputGroup fullWidth className="mb-5 mt-2">
377+
<Label>Key</Label>
378+
<InlineCode variant="base" className="pl-1.5">
379+
{variable.key}
380+
</InlineCode>
381+
</InputGroup>
382+
<EnvironmentCombo environment={variable.environment} className="text-sm" />
383+
384+
<div className="flex justify-between gap-1">
385+
<Switch
386+
variant="small"
387+
label="Reveal"
388+
checked={reveal}
389+
onCheckedChange={(e) => setReveal(e.valueOf())}
390+
/>
391+
</div>
392+
<InputGroup fullWidth>
393+
<Label>Value</Label>
394+
<Input
395+
{...conform.input(value, { type: "text" })}
396+
placeholder={variable.isSecret ? "Set new secret value" : "Not set"}
397+
defaultValue={variable.value}
398+
type={reveal ? "text" : "password"}
399+
/>
400+
<FormError id={value.errorId}>{value.error}</FormError>
401+
</InputGroup>
402+
403+
<FormError>{form.error}</FormError>
404+
405+
<FormButtons
406+
confirmButton={
407+
<Button type="submit" variant="primary/medium" disabled={isLoading}>
408+
{isLoading ? "Saving…" : "Save"}
409+
</Button>
410+
}
411+
cancelButton={
412+
<Button onClick={() => setIsOpen(false)} variant="tertiary/medium" type="button">
413+
Cancel
414+
</Button>
415+
}
416+
/>
417+
</Fieldset>
418+
</Form>
419+
</DialogContent>
420+
</Dialog>
421+
);
422+
}
443423

444424
function DeleteEnvironmentVariableButton({
445425
variable,

0 commit comments

Comments
 (0)