Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,11 @@ import {
} from "@webstudio-is/design-system";
import { EllipsesIcon, PlusIcon } from "@webstudio-is/icons";
import type { DataSource } from "@webstudio-is/sdk";
import {
decodeDataSourceVariable,
findPageByIdOrPath,
getExpressionIdentifiers,
} from "@webstudio-is/sdk";
import { findPageByIdOrPath } from "@webstudio-is/sdk";
import {
$dataSources,
$instances,
$pages,
$props,
$resources,
$variableValuesByInstanceSelector,
Expand All @@ -59,6 +56,7 @@ import { updateWebstudioData } from "~/shared/instance-utils";
import {
deleteVariableMutable,
findAvailableVariables,
findUsedVariables,
} from "~/shared/data-variables";

/**
Expand Down Expand Up @@ -91,71 +89,20 @@ const $instanceVariableValues = computed(
new Map<string, unknown>()
);

/**
* find variables used in
*
* instance children
* expression prop
* action prop
* url resource field
* header resource field
* body resource fiel
*/
const $usedVariables = computed(
[$instances, $props, $resources, $selectedPage],
(instances, props, resources, page) => {
const usedVariables = new Map<DataSource["id"], number>();
const collectExpressionVariables = (expression: string) => {
const identifiers = getExpressionIdentifiers(expression);
for (const identifier of identifiers) {
const id = decodeDataSourceVariable(identifier);
if (id !== undefined) {
const count = usedVariables.get(id) ?? 0;
usedVariables.set(id, count + 1);
}
}
};
for (const instance of instances.values()) {
for (const child of instance.children) {
if (child.type === "expression") {
collectExpressionVariables(child.value);
}
}
}
for (const resource of resources.values()) {
collectExpressionVariables(resource.url);
for (const { value } of resource.headers) {
collectExpressionVariables(value);
}
if (resource.body) {
collectExpressionVariables(resource.body);
}
}
for (const prop of props.values()) {
if (prop.type === "expression") {
collectExpressionVariables(prop.value);
}
if (prop.type === "action") {
for (const value of prop.value) {
collectExpressionVariables(value.code);
}
}
}
if (page) {
collectExpressionVariables(page.title);
collectExpressionVariables(page.meta.description ?? "");
collectExpressionVariables(page.meta.excludePageFromSearch ?? "");
collectExpressionVariables(page.meta.socialImageUrl ?? "");
collectExpressionVariables(page.meta.language ?? "");
collectExpressionVariables(page.meta.status ?? "");
collectExpressionVariables(page.meta.redirect ?? "");
if (page.meta.custom) {
for (const { content } of page.meta.custom) {
collectExpressionVariables(content);
}
}
[$selectedInstance, $pages, $instances, $props, $dataSources, $resources],
(selectedInstance, pages, instances, props, dataSources, resources) => {
if (selectedInstance === undefined) {
return new Map<DataSource["id"], number>();
}
return usedVariables;
return findUsedVariables({
startingInstanceId: selectedInstance.id,
pages,
instances,
props,
dataSources,
resources,
});
}
);

Expand Down
82 changes: 82 additions & 0 deletions apps/builder/app/shared/data-variables.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -656,3 +656,85 @@ test("unset global variables in slots when delete", () => {
{ type: "expression", value: "globalVariable" },
]);
});

test("unset body variables in page meta when delete", () => {
const bodyVariable = new Variable("bodyVariable", "");
const data = {
pages: createDefaultPages({ rootInstanceId: "bodyId" }),
...renderData(
<$.Body ws:id="bodyId" vars={expression`${bodyVariable}`}></$.Body>
),
};
expect(data.dataSources.size).toEqual(1);
const [bodyVariableId] = data.dataSources.keys();
const bodyIdentifier = encodeDataVariableId(bodyVariableId);
data.pages.homePage.title = bodyIdentifier;
data.pages.homePage.meta = {
description: bodyIdentifier,
excludePageFromSearch: bodyIdentifier,
socialImageUrl: bodyIdentifier,
language: bodyIdentifier,
status: bodyIdentifier,
redirect: bodyIdentifier,
custom: [{ property: "auth", content: bodyIdentifier }],
};
deleteVariableMutable(data, bodyVariableId);
expect(data.pages.homePage.title).toEqual(`bodyVariable`);
expect(data.pages.homePage.meta.description).toEqual(`bodyVariable`);
expect(data.pages.homePage.meta.excludePageFromSearch).toEqual(
`bodyVariable`
);
expect(data.pages.homePage.meta.socialImageUrl).toEqual(`bodyVariable`);
expect(data.pages.homePage.meta.language).toEqual(`bodyVariable`);
expect(data.pages.homePage.meta.status).toEqual(`bodyVariable`);
expect(data.pages.homePage.meta.redirect).toEqual(`bodyVariable`);
expect(data.pages.homePage.meta.custom?.[0].content).toEqual(`bodyVariable`);
});

test("unset global variables in all pages meta when delete", () => {
const globalVariable = new Variable("globalVariable", "");
const data = {
pages: createDefaultPages({ rootInstanceId: "homeBodyId" }),
...renderData(
<ws.root ws:id={ROOT_INSTANCE_ID} vars={expression`${globalVariable}`}>
<$.Body ws:id="homeBodyId"></$.Body>
<$.Body ws:id="aboutBodyId"></$.Body>
</ws.root>
),
};
data.instances.delete(ROOT_INSTANCE_ID);
data.pages.pages.push({
id: "",
name: "",
path: "",
title: "",
meta: {},
rootInstanceId: "aboutBodyId",
});
expect(data.dataSources.size).toEqual(1);
const [globalVariableId] = data.dataSources.keys();
const globalIdentifier = encodeDataVariableId(globalVariableId);
for (const page of [data.pages.homePage, ...data.pages.pages]) {
page.title = globalIdentifier;
page.meta = {
description: globalIdentifier,
excludePageFromSearch: globalIdentifier,
socialImageUrl: globalIdentifier,
language: globalIdentifier,
status: globalIdentifier,
redirect: globalIdentifier,
custom: [{ property: "auth", content: globalIdentifier }],
};
}
deleteVariableMutable(data, globalVariableId);
for (const page of [data.pages.homePage, ...data.pages.pages]) {
expect(page.title).toEqual(`globalVariable`);
expect(page.meta.description).toEqual(`globalVariable`);
expect(page.meta.excludePageFromSearch).toEqual(`globalVariable`);
expect(page.meta.socialImageUrl).toEqual(`globalVariable`);
expect(page.meta.language).toEqual(`globalVariable`);
expect(page.meta.status).toEqual(`globalVariable`);
expect(page.meta.redirect).toEqual(`globalVariable`);
expect(page.meta.custom?.[0].content).toEqual(`globalVariable`);
}
});
112 changes: 84 additions & 28 deletions apps/builder/app/shared/data-variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
encodeDataVariableId,
findTreeInstanceIds,
findTreeInstanceIdsExcludingSlotDescendants,
getExpressionIdentifiers,
systemParameter,
transpileExpression,
} from "@webstudio-is/sdk";
Expand Down Expand Up @@ -277,18 +278,54 @@ const traverseExpressions = ({
update: (expression: string, args?: string[]) => void | string;
}) => {
const pagesList = pages ? [pages.homePage, ...pages.pages] : [];

let instanceIds = findTreeInstanceIdsExcludingSlotDescendants(
instances,
startingInstanceId
);
// global variables can be accessed on all pages and inside of slots
if (startingInstanceId === ROOT_INSTANCE_ID) {
for (const page of pagesList) {
for (const page of pagesList) {
// global variables can be accessed on all pages and inside of slots
if (startingInstanceId === ROOT_INSTANCE_ID) {
instanceIds = setUnion(
instanceIds,
findTreeInstanceIds(instances, page.rootInstanceId)
);
}

// global and body variables can be accessed in pages meta
if (
startingInstanceId === page.rootInstanceId ||
startingInstanceId === ROOT_INSTANCE_ID
) {
page.title = update(page.title) ?? page.title;
if (page.meta.description) {
page.meta.description =
update(page.meta.description) ?? page.meta.description;
}
if (page.meta.excludePageFromSearch) {
page.meta.excludePageFromSearch =
update(page.meta.excludePageFromSearch) ??
page.meta.excludePageFromSearch;
}
if (page.meta.socialImageUrl) {
page.meta.socialImageUrl =
update(page.meta.socialImageUrl) ?? page.meta.socialImageUrl;
}
if (page.meta.language) {
page.meta.language = update(page.meta.language) ?? page.meta.language;
}
if (page.meta.status) {
page.meta.status = update(page.meta.status) ?? page.meta.status;
}
if (page.meta.redirect) {
page.meta.redirect = update(page.meta.redirect) ?? page.meta.redirect;
}
if (page.meta.custom) {
for (const item of page.meta.custom) {
item.content = update(item.content) ?? item.content;
}
}
}
}
const resourceIds = new Set<Resource["id"]>();

Expand All @@ -298,10 +335,7 @@ const traverseExpressions = ({
}
for (const child of instance.children) {
if (child.type === "expression") {
const newExpression = update(child.value);
if (newExpression !== undefined) {
child.value = newExpression;
}
child.value = update(child.value) ?? child.value;
}
}
}
Expand All @@ -311,18 +345,12 @@ const traverseExpressions = ({
continue;
}
if (prop.type === "expression") {
const newExpression = update(prop.value);
if (newExpression !== undefined) {
prop.value = newExpression;
}
prop.value = update(prop.value) ?? prop.value;
continue;
}
if (prop.type === "action") {
for (const action of prop.value) {
const newExpression = update(action.code, action.args);
if (newExpression !== undefined) {
action.code = newExpression;
}
action.code = update(action.code, action.args) ?? action.code;
}
continue;
}
Expand All @@ -345,21 +373,12 @@ const traverseExpressions = ({
if (resourceIds.has(resource.id) === false) {
continue;
}
const newExpression = update(resource.url);
if (newExpression !== undefined) {
resource.url = newExpression;
}
resource.url = update(resource.url) ?? resource.url;
for (const header of resource.headers) {
const newExpression = update(header.value);
if (newExpression !== undefined) {
header.value = newExpression;
}
header.value = update(header.value) ?? header.value;
}
if (resource.body) {
const newExpression = update(resource.body);
if (newExpression !== undefined) {
resource.body = newExpression;
}
resource.body = update(resource.body) ?? resource.body;
}
}
};
Expand All @@ -379,7 +398,7 @@ export const findUnsetVariableNames = ({
}) => {
const unsetVariables = new Set<DataSource["name"]>();
traverseExpressions({
startingInstanceId: startingInstanceId,
startingInstanceId,
pages: undefined,
instances,
props,
Expand All @@ -400,6 +419,43 @@ export const findUnsetVariableNames = ({
return Array.from(unsetVariables);
};

export const findUsedVariables = ({
startingInstanceId,
pages,
instances,
props,
dataSources,
resources,
}: {
startingInstanceId: Instance["id"];
pages: undefined | Pages;
instances: Instances;
props: Props;
dataSources: DataSources;
resources: Resources;
}) => {
const usedVariables = new Map<DataSource["id"], number>();
traverseExpressions({
startingInstanceId,
pages,
instances,
props,
dataSources,
resources,
update: (expression) => {
const identifiers = getExpressionIdentifiers(expression);
for (const identifier of identifiers) {
const id = decodeDataVariableId(identifier);
if (id !== undefined) {
const count = usedVariables.get(id) ?? 0;
usedVariables.set(id, count + 1);
}
}
},
});
return usedVariables;
};

export const rebindTreeVariablesMutable = ({
startingInstanceId,
instances,
Expand Down
Loading