diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx
new file mode 100644
index 0000000000..7dacd9c3e8
--- /dev/null
+++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.test.tsx
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ createPlugin as createLegacyPlugin,
+ createRouteRef as createLegacyRouteRef,
+ createRoutableExtension,
+} from '@backstage/core-plugin-api';
+import {
+ createExtensionTester,
+ renderInTestApp,
+} from '@backstage/frontend-test-utils';
+import { screen } from '@testing-library/react';
+import React from 'react';
+import { convertLegacyEntityCardExtension } from './convertLegacyEntityCardExtension';
+import { convertLegacyRouteRef } from '@backstage/core-compat-api';
+import { EntityContentBlueprint } from '../blueprints';
+
+const routeRef = createLegacyRouteRef({ id: 'test' });
+const legacyPlugin = createLegacyPlugin({
+ id: 'test',
+ routes: {
+ test: routeRef,
+ },
+});
+
+describe('convertLegacyEntityCardExtension', () => {
+ it('should convert an entity card extension', async () => {
+ const LegacyExtension = legacyPlugin.provide(
+ createRoutableExtension({
+ name: 'EntityExampleCard',
+ mountPoint: routeRef,
+ component: async () => () =>
Hello
,
+ }),
+ );
+
+ const converted = convertLegacyEntityCardExtension(LegacyExtension);
+ expect(converted.kind).toBe('entity-card');
+ expect(converted.namespace).toBe(undefined);
+ expect(converted.name).toBe('example');
+
+ const tester = createExtensionTester(converted);
+
+ await renderInTestApp(tester.reactElement(), {
+ mountedRoutes: {
+ '/': convertLegacyRouteRef(routeRef),
+ },
+ });
+
+ await expect(screen.findByText('Hello')).resolves.toBeInTheDocument();
+
+ expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe(
+ undefined,
+ );
+ expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe(
+ undefined,
+ );
+ });
+
+ it('should convert an entity card extension with overrides', async () => {
+ const LegacyExtension = legacyPlugin.provide(
+ createRoutableExtension({
+ name: 'EntityExampleCard',
+ mountPoint: routeRef,
+ component: async () => () => Hello
,
+ }),
+ );
+
+ const converted = convertLegacyEntityCardExtension(LegacyExtension, {
+ name: 'other',
+ filter: 'my-filter',
+ });
+ expect(converted.kind).toBe('entity-card');
+ expect(converted.namespace).toBe(undefined);
+ expect(converted.name).toBe('other');
+
+ const tester = createExtensionTester(converted);
+
+ await renderInTestApp(tester.reactElement(), {
+ mountedRoutes: {
+ '/': convertLegacyRouteRef(routeRef),
+ },
+ });
+
+ await expect(screen.findByText('Hello')).resolves.toBeInTheDocument();
+
+ expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe(
+ 'my-filter',
+ );
+ expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe(
+ undefined,
+ );
+ });
+
+ it('should support various naming patterns for entity card extensions', async () => {
+ const withName = (name: string) => {
+ const converted = convertLegacyEntityCardExtension(
+ legacyPlugin.provide(
+ createRoutableExtension({
+ name,
+ mountPoint: routeRef,
+ component: async () => () => Hello
,
+ }),
+ ),
+ );
+ return converted.name;
+ };
+
+ expect(withName('EntityTestCard')).toBe(undefined);
+ expect(withName('EntityTestTrimCard')).toBe('trim');
+ expect(withName('EntityTeStTrimCard')).toBe('trim');
+ expect(withName('EntityExampleCard')).toBe('example');
+ expect(withName('EntityExAmpleCard')).toBe('ex-ample');
+ expect(withName('ExampleCard')).toBe('example-card');
+ });
+});
diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx
index afe5211d1b..76442f526d 100644
--- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx
+++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityCardExtension.tsx
@@ -19,6 +19,7 @@ import { BackstagePlugin, getComponentData } from '@backstage/core-plugin-api';
import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
import React, { ComponentType } from 'react';
import { EntityCardBlueprint } from '../blueprints';
+import kebabCase from 'lodash/kebabCase';
/** @alpha */
export function convertLegacyEntityCardExtension(
@@ -40,7 +41,7 @@ export function convertLegacyEntityCardExtension(
const plugin = getComponentData(element, 'core.plugin');
const pluginId = plugin?.getId();
- const match = extName.match(/^Entity(.*)Content$/);
+ const match = extName.match(/^Entity(.*)Card$/);
const infix = match?.[1] ?? extName;
let name: string | undefined = infix;
@@ -55,6 +56,7 @@ export function convertLegacyEntityCardExtension(
name = undefined;
}
}
+ name = name && kebabCase(name);
return EntityCardBlueprint.make({
name: overrides?.name ?? name,
diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx
new file mode 100644
index 0000000000..6d7c8f0420
--- /dev/null
+++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.test.tsx
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ createPlugin as createLegacyPlugin,
+ createRouteRef as createLegacyRouteRef,
+ createRoutableExtension,
+} from '@backstage/core-plugin-api';
+import { coreExtensionData } from '@backstage/frontend-plugin-api';
+import {
+ createExtensionTester,
+ renderInTestApp,
+} from '@backstage/frontend-test-utils';
+import { screen } from '@testing-library/react';
+import React from 'react';
+import { convertLegacyEntityContentExtension } from './convertLegacyEntityContentExtension';
+import { convertLegacyRouteRef } from '@backstage/core-compat-api';
+import { EntityContentBlueprint } from '../blueprints';
+
+const routeRef = createLegacyRouteRef({ id: 'test' });
+const legacyPlugin = createLegacyPlugin({
+ id: 'test',
+ routes: {
+ test: routeRef,
+ },
+});
+
+describe('convertLegacyEntityContentExtension', () => {
+ it('should convert an entity content extension', async () => {
+ const LegacyExtension = legacyPlugin.provide(
+ createRoutableExtension({
+ name: 'EntityExampleContent',
+ mountPoint: routeRef,
+ component: async () => () => Hello
,
+ }),
+ );
+
+ const converted = convertLegacyEntityContentExtension(LegacyExtension);
+ expect(converted.kind).toBe('entity-content');
+ expect(converted.namespace).toBe(undefined);
+ expect(converted.name).toBe('example');
+
+ const tester = createExtensionTester(converted);
+
+ await renderInTestApp(tester.reactElement(), {
+ mountedRoutes: {
+ '/': convertLegacyRouteRef(routeRef),
+ },
+ });
+
+ await expect(screen.findByText('Hello')).resolves.toBeInTheDocument();
+
+ expect(tester.get(coreExtensionData.routePath)).toBe('/example');
+ expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef);
+ expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe(
+ undefined,
+ );
+ expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe(
+ undefined,
+ );
+ });
+
+ it('should convert an entity content extension with overrides', async () => {
+ const LegacyExtension = legacyPlugin.provide(
+ createRoutableExtension({
+ name: 'EntityExampleContent',
+ mountPoint: routeRef,
+ component: async () => () => Hello
,
+ }),
+ );
+
+ const converted = convertLegacyEntityContentExtension(LegacyExtension, {
+ name: 'other',
+ defaultPath: '/other',
+ defaultTitle: 'Other',
+ filter: 'my-filter',
+ });
+ expect(converted.kind).toBe('entity-content');
+ expect(converted.namespace).toBe(undefined);
+ expect(converted.name).toBe('other');
+
+ const tester = createExtensionTester(converted);
+
+ await renderInTestApp(tester.reactElement(), {
+ mountedRoutes: {
+ '/': convertLegacyRouteRef(routeRef),
+ },
+ });
+
+ await expect(screen.findByText('Hello')).resolves.toBeInTheDocument();
+
+ expect(tester.get(coreExtensionData.routePath)).toBe('/other');
+ expect(tester.get(coreExtensionData.routeRef)).toBe(routeRef);
+ expect(tester.get(EntityContentBlueprint.dataRefs.filterExpression)).toBe(
+ 'my-filter',
+ );
+ expect(tester.get(EntityContentBlueprint.dataRefs.filterFunction)).toBe(
+ undefined,
+ );
+ });
+
+ it('should support various naming patterns for entity content extensions', async () => {
+ const withName = (name: string) => {
+ const converted = convertLegacyEntityContentExtension(
+ legacyPlugin.provide(
+ createRoutableExtension({
+ name,
+ mountPoint: routeRef,
+ component: async () => () => Hello
,
+ }),
+ ),
+ );
+ return converted.name;
+ };
+
+ expect(withName('EntityTestContent')).toBe(undefined);
+ expect(withName('EntityTestTrimContent')).toBe('trim');
+ expect(withName('EntityTeStTrimContent')).toBe('trim');
+ expect(withName('EntityExampleContent')).toBe('example');
+ expect(withName('EntityExAmpleContent')).toBe('ex-ample');
+ expect(withName('ExampleContent')).toBe('example-content');
+ });
+});
diff --git a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx
index a44a6281da..e44b5671fc 100644
--- a/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx
+++ b/plugins/catalog-react/src/alpha/converters/convertLegacyEntityContentExtension.tsx
@@ -71,6 +71,7 @@ export function convertLegacyEntityContentExtension(
name = undefined;
}
}
+ name = name && kebabCase(name);
return EntityContentBlueprint.make({
name: overrides?.name ?? name,