Skip to content
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

Allow variable creation with different target environments #6168

Merged
merged 15 commits into from
Dec 3, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
import { Trash2Icon, Pencil } from "lucide-svelte";
import EditDialog from "./EditDialog.svelte";
import DeleteDialog from "./DeleteDialog.svelte";
import type { VariableNames } from "./types";

export let id: string;
export let environment: string;
export let name: string;
export let value: string;
export let variableNames: string[] = [];
export let variableNames: VariableNames = [];

let isDropdownOpen = false;
let isEditDialogOpen = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</script>

<Tooltip distance={8} location="top" alignment="start">
<div class="text-xs text-gray-500 cursor-pointer">
<div class="text-xs text-gray-500 cursor-pointer w-fit">
{formattedText}
</div>
<TooltipContent slot="tooltip-content">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@
import { defaults, superForm } from "sveltekit-superforms";
import { yup } from "sveltekit-superforms/adapters";
import { object, string, array } from "yup";
import { EnvironmentType } from "./types";
import { type VariableNames } from "./types";
import Input from "@rilldata/web-common/components/forms/Input.svelte";
import IconButton from "@rilldata/web-common/components/button/IconButton.svelte";
import { Trash2Icon, UploadIcon } from "lucide-svelte";
import { getCurrentEnvironment, isDuplicateKey } from "./utils";

export let open = false;
export let variableNames: string[] = [];
export let variableNames: VariableNames = [];

let inputErrors: { [key: number]: boolean } = {};
let isKeyAlreadyExists = false;
Expand Down Expand Up @@ -61,8 +62,9 @@
key: string()
.optional()
.matches(
/^[a-zA-Z0-9_]+$/,
"Key must only contain letters, numbers, and underscores.",
/^[a-zA-Z_][a-zA-Z0-9_.]*$/,
// See: https://github.com/rilldata/rill/pull/6121/files#diff-04140a6ac071a4bac716371f8b66a56c89c9d52cfbf2b05ea1e14ee8d4e301e7R12
"Key must start with a letter or underscore and can only contain letters, digits, underscores, and dots",
),
value: string().optional(),
}),
Expand All @@ -80,41 +82,32 @@
if (!form.valid) return;
const values = form.data;

// Omit draft variables that do not have a key
// Check for duplicates before proceeding
const duplicates = checkForExistingKeys();
if (duplicates > 0) {
// Early return without resetting the form
return;
}

// Only filter and process if there are no duplicates
const filteredVariables = values.variables.filter(
({ key }) => key !== "",
);

// Flatten the variables to match the schema
const flatVariables = Object.fromEntries(
filteredVariables.map(({ key, value }) => [key, value]),
);

try {
await handleUpdateProjectVariables(flatVariables);
open = false;
handleReset();
} catch (error) {
console.error(error);
}
},
});

function getCurrentEnvironment() {
if (isDevelopment && isProduction) {
return undefined;
}

if (isDevelopment) {
return EnvironmentType.DEVELOPMENT;
}

if (isProduction) {
return EnvironmentType.PRODUCTION;
}

return undefined;
}

async function handleUpdateProjectVariables(
flatVariables: AdminServiceUpdateProjectVariablesBodyVariables,
) {
Expand All @@ -123,7 +116,7 @@
organization,
project,
data: {
environment: getCurrentEnvironment(),
environment: getCurrentEnvironment(isDevelopment, isProduction),
variables: flatVariables,
},
});
Expand Down Expand Up @@ -172,17 +165,29 @@
}

function checkForExistingKeys() {
const existingKeys = $form.variables.map((variable) => variable.key);
inputErrors = {};
isKeyAlreadyExists = false;

existingKeys.forEach((key, index) => {
// Case sensitive
if (variableNames.some((existingKey) => existingKey === key)) {
inputErrors[index] = true;
const existingKeys = $form.variables.map((variable) => {
return {
environment: getCurrentEnvironment(isDevelopment, isProduction),
name: variable.key,
};
});

let duplicateCount = 0;
existingKeys.forEach((key, idx) => {
const variableEnvironment = key.environment;
const variableKey = key.name;

if (isDuplicateKey(variableEnvironment, variableKey, variableNames)) {
inputErrors[idx] = true;
isKeyAlreadyExists = true;
duplicateCount++;
}
});

return duplicateCount;
}

function handleFileUpload(event) {
Expand Down Expand Up @@ -356,7 +361,9 @@
{#if isKeyAlreadyExists}
<div class="mt-1">
<p class="text-xs text-red-600 font-normal">
These keys already exist for this project.
{Object.keys(inputErrors).length > 1
? "These keys already exist for your target environment(s)"
: "This key already exists for your target environment(s)"}
</p>
</div>
{/if}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,20 @@
import { defaults, superForm } from "sveltekit-superforms";
import { yup } from "sveltekit-superforms/adapters";
import { object, string } from "yup";
import { EnvironmentType } from "./types";
import { EnvironmentType, type VariableNames } from "./types";
import Input from "@rilldata/web-common/components/forms/Input.svelte";
import { onMount } from "svelte";
import {
getCurrentEnvironment,
getEnvironmentType,
isDuplicateKey,
} from "./utils";

export let open = false;
export let id: string;
export let environment: string;
export let name: string;
export let value: string;
export let variableNames: string[] = [];
export let variableNames: VariableNames = [];

let initialEnvironment: {
isDevelopment: boolean;
Expand All @@ -45,7 +49,7 @@
$: project = $page.params.project;

$: isEnvironmentSelected = isDevelopment || isProduction;
$: hasChanges =
$: hasNewChanges =
$form.key !== initialValues.key ||
$form.value !== initialValues.value ||
initialEnvironment?.isDevelopment !== isDevelopment ||
Expand All @@ -67,8 +71,9 @@
key: string()
.optional()
.matches(
/^[a-zA-Z0-9_]+$/,
"Key must only contain letters, numbers, and underscores",
/^[a-zA-Z_][a-zA-Z0-9_.]*$/,
// See: https://github.com/rilldata/rill/pull/6121/files#diff-04140a6ac071a4bac716371f8b66a56c89c9d52cfbf2b05ea1e14ee8d4e301e7R12
"Key must start with a letter or underscore and can only contain letters, digits, underscores, and dots",
),
value: string().optional(),
}),
Expand All @@ -92,38 +97,27 @@
if (!form.valid) return;
const values = form.data;

checkForExistingKeys();
if (isKeyAlreadyExists) return;

const flatVariable = {
[values.key]: values.value,
};

try {
await handleUpdateProjectVariables(flatVariable);
open = false;
handleReset();
} catch (error) {
console.error(error);
}
},
});

function processEnvironment() {
if (isDevelopment && isProduction) {
return undefined;
}

if (isDevelopment) {
return EnvironmentType.DEVELOPMENT;
}

if (isProduction) {
return EnvironmentType.PRODUCTION;
}

return undefined;
}

async function handleUpdateProjectVariables(
flatVariable: AdminServiceUpdateProjectVariablesBodyVariables,
) {
// Check if the key has changed, if so, check for existing keys
if ($form.key !== initialValues.key) {
checkForExistingKeys();
}
Expand All @@ -146,7 +140,7 @@
organization,
project,
data: {
environment: processEnvironment(),
environment: getCurrentEnvironment(isDevelopment, isProduction),
variables: flatVariable,
},
});
Expand All @@ -155,7 +149,10 @@
// If the key remains the same, update the environment or value
if (initialValues.key === $form.key) {
// If environment has changed, remove the old key and add the new key
if (initialValues.environment !== processEnvironment()) {
if (
initialValues.environment !==
getCurrentEnvironment(isDevelopment, isProduction)
) {
await $updateProjectVariables.mutateAsync({
organization,
project,
Expand All @@ -170,7 +167,7 @@
organization,
project,
data: {
environment: processEnvironment(),
environment: getCurrentEnvironment(isDevelopment, isProduction),
variables: flatVariable,
},
});
Expand Down Expand Up @@ -204,21 +201,30 @@

function handleReset() {
reset();
isDevelopment = false;
isProduction = false;
inputErrors = {};
isKeyAlreadyExists = false;
}

function checkForExistingKeys() {
const existingKeys = [$form.key];
inputErrors = {};
isKeyAlreadyExists = false;

existingKeys.forEach((key, index) => {
// Case sensitive
if (variableNames.some((existingKey) => existingKey === key)) {
inputErrors[index] = true;
isKeyAlreadyExists = true;
}
});
const existingKey = {
environment: getEnvironmentType(
getCurrentEnvironment(isDevelopment, isProduction),
),
name: $form.key,
};

const variableEnvironment = existingKey.environment;
const variableKey = existingKey.name;

if (isDuplicateKey(variableEnvironment, variableKey, variableNames)) {
inputErrors[0] = true;
isKeyAlreadyExists = true;
}
}

function setInitialCheckboxState() {
Expand All @@ -236,13 +242,17 @@
}
}

onMount(() => {
function handleDialogOpen() {
setInitialCheckboxState();
initialEnvironment = {
isDevelopment,
isProduction,
};
});
}

$: if (open) {
handleDialogOpen();
}
</script>

<Dialog
Expand Down Expand Up @@ -298,11 +308,6 @@
: ""}
placeholder="Key"
on:input={(e) => handleKeyChange(e)}
onBlur={() => {
if ($form.key !== initialValues.key) {
checkForExistingKeys();
}
}}
/>
<Input
bind:value={$form.value}
Expand All @@ -322,7 +327,7 @@
{#if isKeyAlreadyExists}
<div class="mt-1">
<p class="text-xs text-red-600 font-normal">
This key already exists for this project.
This key already exists for your target environment(s)
</p>
</div>
{/if}
Expand All @@ -343,7 +348,7 @@
type="primary"
form={$formId}
disabled={$submitting ||
!hasChanges ||
!hasNewChanges ||
!isEnvironmentSelected ||
hasExistingKeys ||
$allErrors.length > 0}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import KeyCell from "./KeyCell.svelte";
import ValueCell from "./ValueCell.svelte";
import ActionsCell from "./ActionsCell.svelte";
import type { VariableNames } from "./types";

export let data: V1ProjectVariable[];
export let emptyText: string = "No environment variables";
export let variableNames: string[] = [];
export let variableNames: VariableNames = [];

const columns: ColumnDef<V1ProjectVariable, any>[] = [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
export let name: string;

// NOTE: if environment is empty, the variable is shared for all environments
function getEnvironmentLabel(environment: string) {
if (environment === "") {
function getEnvironmentType(environment: string) {
if (environment === EnvironmentType.UNDEFINED) {
return "Development, Production";
}
if (environment === EnvironmentType.DEVELOPMENT) {
Expand All @@ -18,7 +18,7 @@
return "";
}

$: environmentLabel = getEnvironmentLabel(environment);
$: environmentLabel = getEnvironmentType(environment);
</script>

<div class="flex flex-col">
Expand Down
Loading
Loading