diff --git a/.changeset/analytics-glimmering-radio-station.md b/.changeset/analytics-glimmering-radio-station.md new file mode 100644 index 0000000000..3460474a9d --- /dev/null +++ b/.changeset/analytics-glimmering-radio-station.md @@ -0,0 +1,6 @@ +--- +'@backstage/cli': patch +--- + +The `create-plugin` command now passes the extension name via the `name` key +in `createRoutableExtension()` calls in newly created plugins. diff --git a/.changeset/analytics-haunts-dismembered-constellations.md b/.changeset/analytics-haunts-dismembered-constellations.md index 6acf7d8bca..851527d852 100644 --- a/.changeset/analytics-haunts-dismembered-constellations.md +++ b/.changeset/analytics-haunts-dismembered-constellations.md @@ -12,7 +12,7 @@ metadata, allowing clicks to be attributed to the plugin containing the link: "action": "click", "subject": "/value/of-the/to-prop/passed-to-the-link", "context": { - "componentName": "SomeAssociatedExtension", + "extension": "SomeAssociatedExtension", "pluginId": "plugin-in-which-link-was-clicked", "routeRef": "any-associated-route-ref-id" } diff --git a/.changeset/analytics-sings-stormy-weather.md b/.changeset/analytics-sings-stormy-weather.md index a04e824b60..4b5eb1f237 100644 --- a/.changeset/analytics-sings-stormy-weather.md +++ b/.changeset/analytics-sings-stormy-weather.md @@ -13,7 +13,7 @@ analytics context, which can be useful for analyzing plugin usage: "action": "navigate", "subject": "/the-path/navigated/to?with=params#and-hashes", "context": { - "componentName": "App", + "extension": "App", "pluginId": "id-of-plugin-that-exported-the-route", "routeRef": "associated-route-ref-id" } diff --git a/packages/cli/templates/default-plugin/src/plugin.ts.hbs b/packages/cli/templates/default-plugin/src/plugin.ts.hbs index dcf35fc5d2..86a8ed9507 100644 --- a/packages/cli/templates/default-plugin/src/plugin.ts.hbs +++ b/packages/cli/templates/default-plugin/src/plugin.ts.hbs @@ -11,6 +11,7 @@ export const {{ pluginVar }} = createPlugin({ export const {{ extensionName }} = {{ pluginVar }}.provide( createRoutableExtension({ + name: '{{ extensionName }}', component: () => import('./components/ExampleComponent').then(m => m.ExampleComponent), mountPoint: rootRouteRef, diff --git a/packages/core-app-api/src/app/App.test.tsx b/packages/core-app-api/src/app/App.test.tsx index b04cf3edbf..cd323fdb6e 100644 --- a/packages/core-app-api/src/app/App.test.tsx +++ b/packages/core-app-api/src/app/App.test.tsx @@ -383,7 +383,7 @@ describe('Integration Test', () => { action: 'navigate', subject: '/', context: { - componentName: 'App', + extension: 'App', pluginId: 'blob', routeRef: 'ref-1-2', }, @@ -392,7 +392,7 @@ describe('Integration Test', () => { action: 'navigate', subject: '/foo', context: { - componentName: 'App', + extension: 'App', pluginId: 'plugin2', routeRef: 'ref-2', }, diff --git a/packages/core-app-api/src/routing/RouteTracker.tsx b/packages/core-app-api/src/routing/RouteTracker.tsx index 5c0cd56862..673772a605 100644 --- a/packages/core-app-api/src/routing/RouteTracker.tsx +++ b/packages/core-app-api/src/routing/RouteTracker.tsx @@ -67,7 +67,7 @@ const getExtensionContext = ( if (plugin && mountPoint) { return { pluginId: plugin.getId(), - componentName: 'App', + extension: 'App', routeRef: mountPoint?.id || '', }; } diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx index e2e8612fc9..9690bdfcba 100644 --- a/packages/core-components/src/components/Link/Link.test.tsx +++ b/packages/core-components/src/components/Link/Link.test.tsx @@ -65,7 +65,6 @@ describe('', () => { expect(analyticsApi.getEvents()[0]).toMatchObject({ action: 'click', subject: '/test', - context: { componentName: 'Link' }, }); // Custom onClick handler should have still been fired too. diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index 1134bc1b07..69da5cd613 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -14,19 +14,17 @@ * limitations under the License. */ -import { useAnalytics, withAnalyticsContext } from '@backstage/core-plugin-api'; +import { useAnalytics } from '@backstage/core-plugin-api'; import { Link as MaterialLink, LinkProps as MaterialLinkProps, } from '@material-ui/core'; -import React, { ElementType, MutableRefObject } from 'react'; +import React, { ElementType } from 'react'; import { Link as RouterLink, LinkProps as RouterLinkProps, } from 'react-router-dom'; -type OptionalRef = MutableRefObject | ((instance: any) => void) | null; - export const isExternalUri = (uri: string) => /^([a-z+.-]+):/.test(uri); export type LinkProps = MaterialLinkProps & @@ -41,8 +39,8 @@ declare function LinkType(props: LinkProps): JSX.Element; * - Makes the Link use react-router * - Captures Link clicks as analytics events. */ -const ActualLink = withAnalyticsContext( - ({ inputRef, onClick, ...props }: LinkProps & { inputRef: OptionalRef }) => { +const ActualLink = React.forwardRef( + ({ onClick, ...props }, ref) => { const analytics = useAnalytics(); const to = String(props.to); const external = isExternalUri(to); @@ -58,7 +56,7 @@ const ActualLink = withAnalyticsContext( return external ? ( // External links ); }, - { componentName: 'Link' }, ); -export const WrappedLink = React.forwardRef((props, ref) => ( - -)); - // TODO(Rugvip): We use this as a workaround to make the exported type be a // function, which makes our API reference docs much nicer. // The first type to be exported gets priority, but it will // be thrown away when compiling to JS. // @ts-ignore -export { LinkType as Link, WrappedLink as Link }; +export { LinkType as Link, ActualLink as Link }; diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index d531244cd7..76411f3f02 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -303,7 +303,7 @@ export type BootErrorPageProps = { export type CommonAnalyticsContext = { pluginId: string; routeRef: string; - componentName: string; + extension: string; }; // Warning: (ae-missing-release-tag) "ConfigApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -964,17 +964,6 @@ export function useRouteRefParams( _routeRef: RouteRef | SubRouteRef, ): Params; -// Warning: (ae-missing-release-tag) "withAnalyticsContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export function withAnalyticsContext

( - Component: React_2.ComponentType

, - values: AnalyticsContextValue, -): { - (props: P): JSX.Element; - displayName: string; -}; - // Warning: (ae-missing-release-tag) "withApis" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/packages/core-plugin-api/src/analytics/AnalyticsContext.test.tsx b/packages/core-plugin-api/src/analytics/AnalyticsContext.test.tsx index cad6d5ec38..88e8afa6ef 100644 --- a/packages/core-plugin-api/src/analytics/AnalyticsContext.test.tsx +++ b/packages/core-plugin-api/src/analytics/AnalyticsContext.test.tsx @@ -17,11 +17,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; -import { - AnalyticsContext, - useAnalyticsContext, - withAnalyticsContext, -} from './AnalyticsContext'; +import { AnalyticsContext, useAnalyticsContext } from './AnalyticsContext'; const AnalyticsSpy = () => { const context = useAnalyticsContext(); @@ -29,7 +25,7 @@ const AnalyticsSpy = () => { <>

{context.routeRef}
{context.pluginId}
-
{context.componentName}
+
{context.extension}
{context.custom}
); @@ -40,7 +36,7 @@ describe('AnalyticsContext', () => { it('returns default values', () => { const { result } = renderHook(() => useAnalyticsContext()); expect(result.current).toEqual({ - componentName: 'App', + extension: 'App', pluginId: 'root', routeRef: 'unknown', }); @@ -55,7 +51,7 @@ describe('AnalyticsContext', () => { , ); - expect(result.getByTestId('component-name')).toHaveTextContent('App'); + expect(result.getByTestId('extension')).toHaveTextContent('App'); expect(result.getByTestId('plugin-id')).toHaveTextContent('root'); expect(result.getByTestId('route-ref')).toHaveTextContent('unknown'); }); @@ -67,7 +63,7 @@ describe('AnalyticsContext', () => { , ); - expect(result.getByTestId('component-name')).toHaveTextContent('App'); + expect(result.getByTestId('extension')).toHaveTextContent('App'); expect(result.getByTestId('plugin-id')).toHaveTextContent('custom'); expect(result.getByTestId('route-ref')).toHaveTextContent('unknown'); }); @@ -75,30 +71,15 @@ describe('AnalyticsContext', () => { it('uses nested analytics context', () => { const result = render( - + , ); - expect(result.getByTestId('component-name')).toHaveTextContent( - 'AnalyticsSpy', - ); + expect(result.getByTestId('extension')).toHaveTextContent('AnalyticsSpy'); expect(result.getByTestId('plugin-id')).toHaveTextContent('custom'); expect(result.getByTestId('route-ref')).toHaveTextContent('unknown'); }); }); - - describe('withAnalyticsContext', () => { - it('wraps component with analytics context', () => { - const AnalyticsSpyHOC = withAnalyticsContext(AnalyticsSpy, { - custom: 'attr', - }); - const result = render(); - expect(result.getByTestId('custom')).toHaveTextContent('attr'); - expect(AnalyticsSpyHOC.displayName).toBe( - 'WithAnalyticsContext(AnalyticsSpy)', - ); - }); - }); }); diff --git a/packages/core-plugin-api/src/analytics/AnalyticsContext.tsx b/packages/core-plugin-api/src/analytics/AnalyticsContext.tsx index f50375d42f..5852dab51c 100644 --- a/packages/core-plugin-api/src/analytics/AnalyticsContext.tsx +++ b/packages/core-plugin-api/src/analytics/AnalyticsContext.tsx @@ -20,7 +20,7 @@ import { AnalyticsContextValue } from './types'; const AnalyticsReactContext = createContext({ routeRef: 'unknown', pluginId: 'root', - componentName: 'App', + extension: 'App', }); /** diff --git a/packages/core-plugin-api/src/analytics/index.ts b/packages/core-plugin-api/src/analytics/index.ts index dbf82aa4e3..651e8e105e 100644 --- a/packages/core-plugin-api/src/analytics/index.ts +++ b/packages/core-plugin-api/src/analytics/index.ts @@ -14,6 +14,6 @@ * limitations under the License. */ -export { AnalyticsContext, withAnalyticsContext } from './AnalyticsContext'; +export { AnalyticsContext } from './AnalyticsContext'; export type { AnalyticsContextValue, CommonAnalyticsContext } from './types'; export { useAnalytics } from './useAnalytics'; diff --git a/packages/core-plugin-api/src/analytics/types.ts b/packages/core-plugin-api/src/analytics/types.ts index 189d182328..045328f613 100644 --- a/packages/core-plugin-api/src/analytics/types.ts +++ b/packages/core-plugin-api/src/analytics/types.ts @@ -29,9 +29,9 @@ export type CommonAnalyticsContext = { routeRef: string; /** - * The name of the associated component. + * The name of the associated extension. */ - componentName: string; + extension: string; }; /** diff --git a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx index ce27602e6c..22021026af 100644 --- a/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx +++ b/packages/core-plugin-api/src/analytics/useAnalytics.test.tsx @@ -55,7 +55,7 @@ describe('useAnalytics', () => { some: 'value', }, context: { - componentName: 'App', + extension: 'App', pluginId: 'root', routeRef: 'unknown', }, diff --git a/packages/core-plugin-api/src/extensions/extensions.test.tsx b/packages/core-plugin-api/src/extensions/extensions.test.tsx index 42a2bf5e66..eff8db5029 100644 --- a/packages/core-plugin-api/src/extensions/extensions.test.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.test.tsx @@ -121,7 +121,7 @@ describe('extensions', () => { <>
{context.pluginId}
{context.routeRef}
-
{context.componentName}
+
{context.extension}
); }, @@ -134,8 +134,6 @@ describe('extensions', () => { expect(result.getByTestId('plugin-id')).toHaveTextContent('my-plugin'); expect(result.getByTestId('route-ref')).toHaveTextContent('some-ref'); - expect(result.getByTestId('component-name')).toHaveTextContent( - 'AnalyticsSpy', - ); + expect(result.getByTestId('extension')).toHaveTextContent('AnalyticsSpy'); }); }); diff --git a/packages/core-plugin-api/src/extensions/extensions.tsx b/packages/core-plugin-api/src/extensions/extensions.tsx index 7aa16f5692..31bb08a13f 100644 --- a/packages/core-plugin-api/src/extensions/extensions.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.tsx @@ -140,7 +140,7 @@ export function createReactExtension< { +export const Playground = () => { return ( <> Click Here ); }; - -export const Playground = withAnalyticsContext(ContextlessPlayground, {}); diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts index 34ee70a3b8..c262849695 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.test.ts @@ -51,7 +51,7 @@ describe('GoogleAnalytics', () => { describe('integration', () => { const context = { - componentName: 'App', + extension: 'App', pluginId: 'some-plugin', releaseNum: 1337, }; @@ -125,7 +125,7 @@ describe('GoogleAnalytics', () => { expect(command).toBe('send'); expect(data).toMatchObject({ hitType: 'event', - eventCategory: context.componentName, + eventCategory: context.extension, eventAction: expectedAction, eventLabel: expectedLabel, eventValue: expectedValue, @@ -178,7 +178,7 @@ describe('GoogleAnalytics', () => { expect(command).toBe('send'); expect(data).toMatchObject({ hitType: 'event', - eventCategory: context.componentName, + eventCategory: context.extension, eventAction: expectedAction, eventLabel: expectedLabel, eventValue: expectedValue, diff --git a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts index dceaf7b2d6..a05b4fe127 100644 --- a/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts +++ b/plugins/analytics-module-ga/src/apis/implementations/AnalyticsApi/GoogleAnalytics.ts @@ -106,7 +106,7 @@ export class GoogleAnalytics implements AnalyticsApi { }: AnalyticsEvent) { const customMetadata = this.getCustomDimensionMetrics(context, attributes); - if (action === 'navigate' && context?.componentName === 'App') { + if (action === 'navigate' && context?.extension === 'App') { // Set any/all custom dimensions. if (Object.keys(customMetadata).length) { ReactGA.set(customMetadata); @@ -117,7 +117,7 @@ export class GoogleAnalytics implements AnalyticsApi { } ReactGA.event({ - category: context.componentName || 'App', + category: context.extension || 'App', action, label: subject, value,