Skip to content

Commit

Permalink
feat: forbid data variables with the same name on instance (#4771)
Browse files Browse the repository at this point in the history
Ref #4768

We don't want to allow data variables on one instance with the same
name. Though we will allow child variables to mask parent ones in
another PR.


To test:
- create variable var1 on box
- try to create another variable with var1
- create variable var2
- try to rename it to var1
- create variable on body with var1
  • Loading branch information
TrySound authored Jan 22, 2025
1 parent aa8a720 commit 7376743
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from "zod";
import { nanoid } from "nanoid";
import { computed } from "nanostores";
import { useStore } from "@nanostores/react";
import {
type ReactNode,
Expand All @@ -12,6 +13,7 @@ import {
useRef,
createContext,
useEffect,
useCallback,
} from "react";
import { CopyIcon, RefreshIcon, UpgradeIcon } from "@webstudio-is/icons";
import {
Expand Down Expand Up @@ -55,7 +57,7 @@ import {
$userPlanFeatures,
} from "~/shared/nano-states";
import { serverSyncStore } from "~/shared/sync";

import { $selectedInstance } from "~/shared/awareness";
import { BindingPopoverProvider } from "~/builder/shared/binding-popover";
import {
EditorDialog,
Expand All @@ -68,18 +70,46 @@ import {
SystemResourceForm,
} from "./resource-panel";
import { generateCurl } from "./curl";
import { $selectedInstance } from "~/shared/awareness";

const validateName = (value: string) =>
value.trim().length === 0 ? "Name is required" : "";
const $variablesByName = computed(
[$selectedInstance, $dataSources],
(instance, dataSources) => {
const variablesByName = new Map<DataSource["name"], DataSource["id"]>();
for (const dataSource of dataSources.values()) {
if (dataSource.scopeInstanceId === instance?.id) {
variablesByName.set(dataSource.name, dataSource.id);
}
}
return variablesByName;
}
);

const NameField = ({ defaultValue }: { defaultValue: string }) => {
const NameField = ({
variableId,
defaultValue,
}: {
variableId: undefined | DataSource["id"];
defaultValue: string;
}) => {
const ref = useRef<HTMLInputElement>(null);
const [error, setError] = useState("");
const nameId = useId();
const variablesByName = useStore($variablesByName);
const validateName = useCallback(
(value: string) => {
if (variablesByName.get(value) !== variableId) {
return "Name is already used by another variable on this instance";
}
if (value.trim().length === 0) {
return "Name is required";
}
return "";
},
[variablesByName, variableId]
);
useEffect(() => {
ref.current?.setCustomValidity(validateName(defaultValue));
}, [defaultValue]);
}, [defaultValue, validateName]);
return (
<Grid gap={1}>
<Label htmlFor={nameId}>Name</Label>
Expand Down Expand Up @@ -510,15 +540,21 @@ const VariablePanel = forwardRef<
if (variableType === "parameter") {
return (
<>
<NameField defaultValue={variable?.name ?? ""} />
<NameField
variableId={variable?.id}
defaultValue={variable?.name ?? ""}
/>
<ParameterForm ref={ref} variable={variable} />
</>
);
}
if (variableType === "string") {
return (
<>
<NameField defaultValue={variable?.name ?? ""} />
<NameField
variableId={variable?.id}
defaultValue={variable?.name ?? ""}
/>
<TypeField value={variableType} onChange={setVariableType} />
<StringForm ref={ref} variable={variable} />
</>
Expand All @@ -527,7 +563,10 @@ const VariablePanel = forwardRef<
if (variableType === "number") {
return (
<>
<NameField defaultValue={variable?.name ?? ""} />
<NameField
variableId={variable?.id}
defaultValue={variable?.name ?? ""}
/>
<TypeField value={variableType} onChange={setVariableType} />
<NumberForm ref={ref} variable={variable} />
</>
Expand All @@ -536,7 +575,10 @@ const VariablePanel = forwardRef<
if (variableType === "boolean") {
return (
<>
<NameField defaultValue={variable?.name ?? ""} />
<NameField
variableId={variable?.id}
defaultValue={variable?.name ?? ""}
/>
<TypeField value={variableType} onChange={setVariableType} />
<BooleanForm ref={ref} variable={variable} />
</>
Expand All @@ -545,7 +587,10 @@ const VariablePanel = forwardRef<
if (variableType === "json") {
return (
<>
<NameField defaultValue={variable?.name ?? ""} />
<NameField
variableId={variable?.id}
defaultValue={variable?.name ?? ""}
/>
<TypeField value={variableType} onChange={setVariableType} />
<JsonForm ref={ref} variable={variable} />
</>
Expand All @@ -555,7 +600,10 @@ const VariablePanel = forwardRef<
if (variableType === "resource") {
return (
<>
<NameField defaultValue={variable?.name ?? ""} />
<NameField
variableId={variable?.id}
defaultValue={variable?.name ?? ""}
/>
<TypeField value={variableType} onChange={setVariableType} />
<ResourceForm ref={ref} variable={variable} />
</>
Expand All @@ -565,7 +613,10 @@ const VariablePanel = forwardRef<
if (variableType === "graphql-resource") {
return (
<>
<NameField defaultValue={variable?.name ?? ""} />
<NameField
variableId={variable?.id}
defaultValue={variable?.name ?? ""}
/>
<TypeField value={variableType} onChange={setVariableType} />
<GraphqlResourceForm ref={ref} variable={variable} />
</>
Expand All @@ -575,7 +626,10 @@ const VariablePanel = forwardRef<
if (variableType === "system-resource") {
return (
<>
<NameField defaultValue={variable?.name ?? ""} />
<NameField
variableId={variable?.id}
defaultValue={variable?.name ?? ""}
/>
<TypeField value={variableType} onChange={setVariableType} />
<SystemResourceForm ref={ref} variable={variable} />
</>
Expand Down
2 changes: 1 addition & 1 deletion apps/builder/app/builder/shared/binding-popover.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, test } from "vitest";
import { evaluateExpressionWithinScope } from "./binding-popover";
import { encodeDataSourceVariable } from "@webstudio-is/sdk";
import { evaluateExpressionWithinScope } from "./binding-popover";

test("evaluateExpressionWithinScope works", () => {
const variableName = "jsonVariable";
Expand Down
8 changes: 4 additions & 4 deletions apps/builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "^1.4.0",
"@codemirror/autocomplete": "^6.18.4",
"@codemirror/commands": "^6.7.1",
"@codemirror/commands": "^6.8.0",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.2",
"@codemirror/lang-markdown": "^6.3.1",
"@codemirror/lang-markdown": "^6.3.2",
"@codemirror/language": "^6.10.8",
"@codemirror/lint": "^6.8.4",
"@codemirror/state": "^6.5.0",
"@codemirror/view": "^6.36.1",
"@codemirror/state": "^6.5.1",
"@codemirror/view": "^6.36.2",
"@floating-ui/dom": "^1.6.12",
"@fontsource-variable/inter": "^5.0.20",
"@fontsource-variable/manrope": "^5.0.20",
Expand Down
78 changes: 39 additions & 39 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7376743

Please sign in to comment.