mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-10-23 03:27:27 +09:00
fix: Hide deprecated components in Builder (#6576)
The Components panel now hides deprecated components by default. The default-off `deprecatedComponents` feature flag shows all nine registered deprecated components in a separate **Deprecated** section, including Webhook Form and RemixForm. Webhook Form keeps its saved component ID, icon, behavior, navigator label, and `w-webhook-form` CSS class. The command picker continues to hide deprecated components. ## Checklist - [ ] Document each feature flag beside its declaration using its current behavior. - [x] Keep Webhook Form metadata and picker guidance pointing to Form. - [x] Preserve the generated `w-webhook-form` class without custom `presetClassName` metadata. - [x] Hide deprecated components from the Components panel by default; show all nine eligible components in a dedicated Deprecated section behind the default-off `deprecatedComponents` flag. - [x] Keep deprecated components out of the command picker. - [x] Add focused tests for default and flagged visibility, section membership, exclusions, and template insertion data. - [x] Review documentation impact. Existing Webhook Form docs describe its default unavailability; the flag is for development, so no docs change is needed. - [x] Verify focused tests, typechecks, lint, and fixture generation. The fixture pipeline passed; its unrelated live fixture path drift was restored. - [x] Update the Components panel E2E filter assertion for the flagged deprecated section and verify the rerun. - [x] Verify required CI on the latest head. Focused tests: component catalog 16/16, Builder 3/3. Builder, project-build, and feature-flags typechecks passed. Lint and the full fixture pipeline passed. The new section tests failed as expected with section creation temporarily disabled and passed after restoration. Latest-head CI: Main workflow, Visual regression, Lint PR, and Check submodules all passed, including all six Builder E2E shards. Ref #4093
This commit is contained in:
1 parent
534401c66b
commit
dabaeede02
8 files changed
+194
-13
No files matched your search
@@ -47,6 +47,7 @@ const $metas = computed(
|
||||
const metasByCategory = listBuilderComponentPanelItems({
|
||||
metas: componentMetas,
|
||||
templates,
|
||||
showDeprecatedSection: isFeatureEnabled("deprecatedComponents"),
|
||||
documentType: selectedPage?.meta.documentType ?? "html",
|
||||
showInternal: isFeatureEnabled("internalComponents"),
|
||||
getFallbackLabel: (component) => getInstanceLabel({ component }),
|
||||
|
||||
@@ -5,6 +5,11 @@ import { CodeText } from "@webstudio-is/sdk-components-react/components";
|
||||
import { canvasComponentLibraries } from "@webstudio-is/sdk-components-registry/canvas";
|
||||
import { componentIds } from "@webstudio-is/sdk-components-registry/components";
|
||||
import { getInstanceLabel } from "~/builder/shared/instance-label";
|
||||
import { $componentOptions } from "~/builder/features/command-panel/groups/components-group";
|
||||
import {
|
||||
closeCommandPanel,
|
||||
openCommandPanel,
|
||||
} from "~/builder/features/command-panel/command-state";
|
||||
import {
|
||||
$registeredComponentMetas,
|
||||
$registeredComponents,
|
||||
@@ -13,11 +18,27 @@ import {
|
||||
} from "./components";
|
||||
|
||||
afterEach(() => {
|
||||
closeCommandPanel();
|
||||
$registeredComponents.set(new Map());
|
||||
$registeredComponentMetas.set(new Map());
|
||||
$registeredTemplates.set(new Map());
|
||||
});
|
||||
|
||||
test("hides deprecated components from the command panel", () => {
|
||||
for (const library of canvasComponentLibraries) {
|
||||
registerComponentLibrary(library);
|
||||
}
|
||||
openCommandPanel();
|
||||
|
||||
expect(
|
||||
$componentOptions
|
||||
.get()
|
||||
.filter(
|
||||
({ name }) => $registeredComponentMetas.get().get(name)?.deprecated
|
||||
)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
test("renders templates that import an alternate registered implementation", () => {
|
||||
registerComponentLibrary({
|
||||
components: {},
|
||||
@@ -35,7 +56,7 @@ test("renders templates that import an alternate registered implementation", ()
|
||||
expect(template?.instances[0]?.component).toBe("CodeText");
|
||||
});
|
||||
|
||||
test("lists the managed Form while retaining saved Webhook Forms", () => {
|
||||
test("lists all deprecated components only in the flagged section", () => {
|
||||
for (const library of canvasComponentLibraries) {
|
||||
registerComponentLibrary(library);
|
||||
}
|
||||
@@ -57,10 +78,49 @@ test("lists the managed Form while retaining saved Webhook Forms", () => {
|
||||
}),
|
||||
]);
|
||||
|
||||
// New sites use Form while saved Webhook Forms remain registered for rendering.
|
||||
expect(itemsByCategory.has("deprecated")).toBe(false);
|
||||
const flaggedItemsByCategory = listBuilderComponentPanelItems({
|
||||
metas: $registeredComponentMetas.get(),
|
||||
templates: $registeredTemplates.get(),
|
||||
showDeprecatedSection: true,
|
||||
getFallbackLabel: (component) => getInstanceLabel({ component }),
|
||||
getMetaLabel: (component) => getInstanceLabel({ component }),
|
||||
});
|
||||
expect(flaggedItemsByCategory.get("deprecated")).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: "WebhookForm",
|
||||
label: "Webhook Form (Deprecated)",
|
||||
firstInstance: expect.objectContaining({ component: "WebhookForm" }),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "RemixForm",
|
||||
firstInstance: { component: "RemixForm" },
|
||||
}),
|
||||
])
|
||||
);
|
||||
expect(
|
||||
itemsByCategory.get("data")?.some(({ name }) => name === "WebhookForm")
|
||||
).toBe(false);
|
||||
flaggedItemsByCategory
|
||||
.get("deprecated")
|
||||
?.map(({ name }) => name)
|
||||
.sort()
|
||||
).toEqual([
|
||||
"Blockquote",
|
||||
"Box",
|
||||
"Heading",
|
||||
"Link",
|
||||
"List",
|
||||
"Paragraph",
|
||||
"RemixForm",
|
||||
"RichTextLink",
|
||||
"WebhookForm",
|
||||
]);
|
||||
expect(
|
||||
Array.from(flaggedItemsByCategory.entries())
|
||||
.filter(([category]) => category !== "deprecated")
|
||||
.flatMap(([, items]) => items.map(({ name }) => name))
|
||||
.filter((name) => $registeredComponentMetas.get().get(name)?.deprecated)
|
||||
).toEqual([]);
|
||||
|
||||
// Saved instances keep their original component IDs and implementations.
|
||||
expect($registeredComponents.get().has("ResourceForm")).toBe(true);
|
||||
|
||||
@@ -280,7 +280,6 @@ test("Builder Components panel filters catalog-only entries and inserts a persis
|
||||
"pages actions verify components panel catalog filters",
|
||||
async () => {
|
||||
await openComponentsPanel({ page });
|
||||
await expectComponentPanelOptionHidden({ page, name: "Box" });
|
||||
await expectComponentPanelOptionHidden({ page, name: "Body" });
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,3 +3,4 @@ export const internalComponents = false;
|
||||
export const unsupportedBrowsers = false;
|
||||
export const resourceProp = false;
|
||||
export const multiplayer = false;
|
||||
export const deprecatedComponents = false;
|
||||
@@ -521,6 +521,23 @@ describe("component catalog", () => {
|
||||
.flat()
|
||||
.map((item) => item.name)
|
||||
).not.toEqual(expect.arrayContaining(["Box", "HiddenWidget"]));
|
||||
|
||||
const panelItemsWithDeprecatedBox = listBuilderComponentPanelItems({
|
||||
metas,
|
||||
templates,
|
||||
showDeprecatedSection: true,
|
||||
});
|
||||
expect(
|
||||
panelItemsWithDeprecatedBox.get("general")?.map((item) => item.name)
|
||||
).toEqual(["LocalTemplateOnly"]);
|
||||
expect(
|
||||
panelItemsWithDeprecatedBox.get("deprecated")?.map((item) => item.name)
|
||||
).toEqual(["Box"]);
|
||||
expect(
|
||||
Array.from(panelItemsWithDeprecatedBox.values())
|
||||
.flat()
|
||||
.map((item) => item.name)
|
||||
).not.toContain("HiddenWidget");
|
||||
});
|
||||
|
||||
test("preserves builder components panel raw grouping semantics", () => {
|
||||
@@ -594,6 +611,61 @@ describe("component catalog", () => {
|
||||
expect(itemsByCategory.has("hidden")).toBe(false);
|
||||
});
|
||||
|
||||
test("keeps unavailable deprecated components out of the opt-in section", () => {
|
||||
const metas = new Map<string, ComponentCatalogMeta>([
|
||||
["Legacy", { deprecated: true }],
|
||||
["Hidden", { category: "hidden", deprecated: true }],
|
||||
["Internal", { category: "internal", deprecated: true }],
|
||||
["Xml", { category: "xml", deprecated: true }],
|
||||
[
|
||||
"Part",
|
||||
{ contentModel: { category: "none", children: [] }, deprecated: true },
|
||||
],
|
||||
]);
|
||||
const templates = new Map<string, ComponentCatalogTemplate>([
|
||||
[
|
||||
"Legacy",
|
||||
{ category: "general", template: createEmptyWebstudioFragment() },
|
||||
],
|
||||
[
|
||||
"Hidden",
|
||||
{ category: "general", template: createEmptyWebstudioFragment() },
|
||||
],
|
||||
[
|
||||
"Xml",
|
||||
{ category: "general", template: createEmptyWebstudioFragment() },
|
||||
],
|
||||
[
|
||||
"HiddenTemplate",
|
||||
{ category: "hidden", template: createEmptyWebstudioFragment() },
|
||||
],
|
||||
]);
|
||||
metas.set("HiddenTemplate", { deprecated: true });
|
||||
|
||||
const items = listBuilderComponentPanelItems({
|
||||
metas,
|
||||
templates,
|
||||
showDeprecatedSection: true,
|
||||
});
|
||||
expect(items.get("deprecated")?.map(({ name }) => name)).toEqual([
|
||||
"Legacy",
|
||||
]);
|
||||
expect(
|
||||
items.get("general")?.some(({ name }) => name === "Legacy") ?? false
|
||||
).toBe(false);
|
||||
expect(
|
||||
listBuilderComponentPanelItems({ metas, templates }).has("deprecated")
|
||||
).toBe(false);
|
||||
expect(
|
||||
listBuilderComponentPanelItems({
|
||||
metas,
|
||||
templates,
|
||||
showDeprecatedSection: true,
|
||||
documentType: "xml",
|
||||
}).has("deprecated")
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test("filters catalog items by visibility and document type", () => {
|
||||
const metas = new Map<string, ComponentCatalogMeta>([
|
||||
["Body", { category: "hidden" }],
|
||||
|
||||
@@ -404,9 +404,12 @@ export const isComponentDeprecatedInCatalog = (
|
||||
|
||||
export const isComponentMetaUnavailableInCatalog = (
|
||||
meta: ComponentCatalogMeta,
|
||||
{ showInternal = false }: { showInternal?: boolean } = {}
|
||||
{
|
||||
showInternal = false,
|
||||
includeDeprecated = false,
|
||||
}: { showInternal?: boolean; includeDeprecated?: boolean } = {}
|
||||
) => {
|
||||
if (isComponentDeprecatedInCatalog(meta)) {
|
||||
if (isComponentDeprecatedInCatalog(meta) && !includeDeprecated) {
|
||||
return true;
|
||||
}
|
||||
if (meta.category !== undefined) {
|
||||
@@ -716,6 +719,7 @@ export const listBuilderComponentPanelItems = ({
|
||||
templates,
|
||||
documentType = "html",
|
||||
showInternal = false,
|
||||
showDeprecatedSection = false,
|
||||
getFallbackLabel = (component) => component,
|
||||
getMetaLabel = (component) => getFallbackLabel(component),
|
||||
getTemplateIcon = (_component, template) => template.icon,
|
||||
@@ -724,6 +728,7 @@ export const listBuilderComponentPanelItems = ({
|
||||
templates: ReadonlyMap<string, ComponentCatalogTemplate>;
|
||||
documentType?: ComponentCatalogDocumentType;
|
||||
showInternal?: boolean;
|
||||
showDeprecatedSection?: boolean;
|
||||
getFallbackLabel?: (component: string) => string;
|
||||
getMetaLabel?: (component: string, meta: ComponentCatalogMeta) => string;
|
||||
getTemplateIcon?: (
|
||||
@@ -734,12 +739,25 @@ export const listBuilderComponentPanelItems = ({
|
||||
}) => {
|
||||
const items: BuilderComponentPanelItem[] = [];
|
||||
for (const [name, meta] of metas) {
|
||||
if (isComponentDeprecatedInCatalog(meta)) {
|
||||
const deprecated = isComponentDeprecatedInCatalog(meta);
|
||||
if (deprecated && !showDeprecatedSection) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
deprecated &&
|
||||
(meta.category === "hidden" ||
|
||||
meta.category === "xml" ||
|
||||
(meta.category === "internal" && !showInternal) ||
|
||||
meta.contentModel?.category === "none")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (deprecated && templates.has(name)) {
|
||||
continue;
|
||||
}
|
||||
items.push({
|
||||
name,
|
||||
category: meta.category ?? "hidden",
|
||||
category: deprecated ? "deprecated" : (meta.category ?? "hidden"),
|
||||
order: meta.order,
|
||||
label: getMetaLabel(name, meta),
|
||||
description: meta.description,
|
||||
@@ -748,12 +766,32 @@ export const listBuilderComponentPanelItems = ({
|
||||
}
|
||||
for (const [name, template] of templates) {
|
||||
const meta = metas.get(name);
|
||||
if (meta !== undefined && isComponentMetaUnavailableInCatalog(meta)) {
|
||||
const deprecated = isComponentDeprecatedInCatalog(meta);
|
||||
if (deprecated && !showDeprecatedSection) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
deprecated &&
|
||||
(template.category === "hidden" ||
|
||||
template.category === "xml" ||
|
||||
(template.category === "internal" && !showInternal) ||
|
||||
meta?.category === "xml" ||
|
||||
meta?.contentModel?.category === "none")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
meta !== undefined &&
|
||||
isComponentMetaUnavailableInCatalog(meta, {
|
||||
includeDeprecated: deprecated,
|
||||
showInternal: deprecated && showInternal,
|
||||
})
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
items.push({
|
||||
name,
|
||||
category: template.category ?? "hidden",
|
||||
category: deprecated ? "deprecated" : (template.category ?? "hidden"),
|
||||
order: template.order,
|
||||
label: template.label ?? meta?.label ?? getFallbackLabel(name),
|
||||
description: template.description,
|
||||
@@ -798,6 +836,14 @@ export const listBuilderComponentPanelItems = ({
|
||||
});
|
||||
itemsByCategory.set(category, categoryItems);
|
||||
}
|
||||
if (showDeprecatedSection && documentType === "html") {
|
||||
const deprecatedItems = items.filter(
|
||||
(item) => item.category === "deprecated"
|
||||
);
|
||||
if (deprecatedItems.length > 0) {
|
||||
itemsByCategory.set("deprecated", deprecatedItems);
|
||||
}
|
||||
}
|
||||
return itemsByCategory;
|
||||
};
|
||||
|
||||
|
||||
@@ -17,7 +17,9 @@ const formState = new Variable("formState", "initial");
|
||||
export const meta: TemplateMeta = {
|
||||
category: "data",
|
||||
order: 1,
|
||||
description: "Collect user data and send it to any webhook.",
|
||||
label: "Webhook Form (Deprecated)",
|
||||
description:
|
||||
"Deprecated. Existing Webhook Forms remain supported through the next release. Use the new Form component in the Forms section instead.",
|
||||
template: (
|
||||
<WebhookForm
|
||||
state={expression`${formState}`}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { props } from "./__generated__/webhook-form.props";
|
||||
export const meta: WsComponentMeta = {
|
||||
label: "Webhook Form",
|
||||
description:
|
||||
"Deprecated. Existing Webhook Forms remain supported through the next release. Use Form for new forms.",
|
||||
"Deprecated. Existing Webhook Forms remain supported through the next release. Use the new Form component in the Forms section instead.",
|
||||
deprecated: true,
|
||||
icon: WebhookFormIcon,
|
||||
presetStyle: {
|
||||
|
||||
Reference in new issue
Block a user