Make componetName == extension (name)
Signed-off-by: Eric Peterson <ericpeterson@spotify.com>
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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, {});
|
||||
|
||||
+3
-3
@@ -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,
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user