Make componetName == extension (name)

Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
Eric Peterson
2021-09-16 20:15:28 +02:00
parent 11582dacc2
commit ae4680b88d
19 changed files with 41 additions and 77 deletions
@@ -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.
@@ -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"
}
+1 -1
View File
@@ -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"
}
@@ -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,
+2 -2
View File
@@ -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',
},
@@ -67,7 +67,7 @@ const getExtensionContext = (
if (plugin && mountPoint) {
return {
pluginId: plugin.getId(),
componentName: 'App',
extension: 'App',
routeRef: mountPoint?.id || '',
};
}
@@ -65,7 +65,6 @@ describe('<Link />', () => {
expect(analyticsApi.getEvents()[0]).toMatchObject({
action: 'click',
subject: '/test',
context: { componentName: 'Link' },
});
// Custom onClick handler should have still been fired too.
@@ -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<any> | ((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<any, LinkProps>(
({ 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
<MaterialLink
ref={inputRef}
ref={ref}
href={to}
onClick={handleClick}
{...(newWindow ? { target: '_blank', rel: 'noopener' } : {})}
@@ -67,23 +65,18 @@ const ActualLink = withAnalyticsContext(
) : (
// Interact with React Router for internal links
<MaterialLink
ref={inputRef}
ref={ref}
component={RouterLink}
onClick={handleClick}
{...props}
/>
);
},
{ componentName: 'Link' },
);
export const WrappedLink = React.forwardRef<any, LinkProps>((props, ref) => (
<ActualLink {...props} inputRef={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 };
+1 -12
View File
@@ -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<Params extends AnyParams>(
_routeRef: RouteRef<Params> | SubRouteRef<Params>,
): 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<P>(
Component: React_2.ComponentType<P>,
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)
@@ -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 = () => {
<>
<div data-testid="route-ref">{context.routeRef}</div>
<div data-testid="plugin-id">{context.pluginId}</div>
<div data-testid="component-name">{context.componentName}</div>
<div data-testid="extension">{context.extension}</div>
<div data-testid="custom">{context.custom}</div>
</>
);
@@ -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', () => {
</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', () => {
</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(
<AnalyticsContext attributes={{ pluginId: 'custom' }}>
<AnalyticsContext attributes={{ componentName: 'AnalyticsSpy' }}>
<AnalyticsContext attributes={{ extension: 'AnalyticsSpy' }}>
<AnalyticsSpy />
</AnalyticsContext>
</AnalyticsContext>,
);
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(<AnalyticsSpyHOC />);
expect(result.getByTestId('custom')).toHaveTextContent('attr');
expect(AnalyticsSpyHOC.displayName).toBe(
'WithAnalyticsContext(AnalyticsSpy)',
);
});
});
});
@@ -20,7 +20,7 @@ import { AnalyticsContextValue } from './types';
const AnalyticsReactContext = createContext<AnalyticsContextValue>({
routeRef: 'unknown',
pluginId: 'root',
componentName: 'App',
extension: 'App',
});
/**
@@ -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';
@@ -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;
};
/**
@@ -55,7 +55,7 @@ describe('useAnalytics', () => {
some: 'value',
},
context: {
componentName: 'App',
extension: 'App',
pluginId: 'root',
routeRef: 'unknown',
},
@@ -121,7 +121,7 @@ describe('extensions', () => {
<>
<div data-testid="plugin-id">{context.pluginId}</div>
<div data-testid="route-ref">{context.routeRef}</div>
<div data-testid="component-name">{context.componentName}</div>
<div data-testid="extension">{context.extension}</div>
</>
);
},
@@ -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');
});
});
@@ -140,7 +140,7 @@ export function createReactExtension<
<AnalyticsContext
attributes={{
pluginId: plugin.getId(),
componentName,
...(options.name ? { extension: options.name } : {}),
...(data['core.mountpoint']
? {
routeRef: (data['core.mountpoint'] as { id?: string })
@@ -15,15 +15,12 @@
*/
import React from 'react';
import { withAnalyticsContext } from '@backstage/core-plugin-api';
import { Link } from '@backstage/core-components';
const ContextlessPlayground = () => {
export const Playground = () => {
return (
<>
<Link to="#clicked">Click Here</Link>
</>
);
};
export const Playground = withAnalyticsContext(ContextlessPlayground, {});
@@ -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,
@@ -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,