Merge pull request #14573 from backstage/techdocs/entity-ref-analytics
[TechDocs] Analytics entity-awareness
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
An analytics event matching the semantics of the `click` action is now captured when users click links within a TechDocs document.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs-react': patch
|
||||
---
|
||||
|
||||
Analytics events captured within the `<TechDocsReaderPageProvider>` now include the conventional `entityRef` context value, associating those events with their corresponding entity.
|
||||
@@ -19,9 +19,13 @@ import { renderHook, act } from '@testing-library/react-hooks';
|
||||
import { ThemeProvider } from '@material-ui/core';
|
||||
|
||||
import { lightTheme } from '@backstage/theme';
|
||||
import { TestApiProvider } from '@backstage/test-utils';
|
||||
import { MockAnalyticsApi, TestApiProvider } from '@backstage/test-utils';
|
||||
import { Entity, CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import { configApiRef } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
analyticsApiRef,
|
||||
configApiRef,
|
||||
useAnalytics,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { techdocsApiRef } from './api';
|
||||
import { useTechDocsReaderPage, TechDocsReaderPageProvider } from './context';
|
||||
@@ -60,6 +64,8 @@ const configApiMock = {
|
||||
getOptionalBoolean: jest.fn().mockReturnValue(undefined),
|
||||
};
|
||||
|
||||
const analyticsApiMock = new MockAnalyticsApi();
|
||||
|
||||
const wrapper = ({
|
||||
entityRef = {
|
||||
kind: mockEntityMetadata.kind,
|
||||
@@ -74,6 +80,7 @@ const wrapper = ({
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<TestApiProvider
|
||||
apis={[
|
||||
[analyticsApiRef, analyticsApiMock],
|
||||
[configApiRef, configApiMock],
|
||||
[techdocsApiRef, techdocsApiMock],
|
||||
]}
|
||||
@@ -86,6 +93,10 @@ const wrapper = ({
|
||||
);
|
||||
|
||||
describe('useTechDocsReaderPage', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should set title', async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
() => useTechDocsReaderPage(),
|
||||
@@ -157,4 +168,21 @@ describe('useTechDocsReaderPage', () => {
|
||||
|
||||
expect(result.current.entityRef).toStrictEqual(caseSensitiveEntityRef);
|
||||
});
|
||||
|
||||
it('entityRef provided as analytics context', async () => {
|
||||
const { waitForNextUpdate } = renderHook(
|
||||
() => useAnalytics().captureEvent('action', 'subject'),
|
||||
{ wrapper },
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(analyticsApiMock.getEvents()[0]).toMatchObject({
|
||||
action: 'action',
|
||||
subject: 'subject',
|
||||
context: {
|
||||
entityRef: 'component:default/test',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,7 +33,11 @@ import {
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
|
||||
import { configApiRef, useApi } from '@backstage/core-plugin-api';
|
||||
import {
|
||||
AnalyticsContext,
|
||||
configApiRef,
|
||||
useApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
|
||||
import { techdocsApiRef } from './api';
|
||||
import { TechDocsEntityMetadata, TechDocsMetadata } from './types';
|
||||
@@ -141,9 +145,13 @@ export const TechDocsReaderPageProvider = memo(
|
||||
const versionedValue = createVersionedValueMap({ 1: value });
|
||||
|
||||
return (
|
||||
<TechDocsReaderPageContext.Provider value={versionedValue}>
|
||||
{children instanceof Function ? children(value) : children}
|
||||
</TechDocsReaderPageContext.Provider>
|
||||
<AnalyticsContext
|
||||
attributes={{ entityRef: stringifyEntityRef(entityRef) }}
|
||||
>
|
||||
<TechDocsReaderPageContext.Provider value={versionedValue}>
|
||||
{children instanceof Function ? children(value) : children}
|
||||
</TechDocsReaderPageContext.Provider>
|
||||
</AnalyticsContext>
|
||||
);
|
||||
},
|
||||
(prevProps, nextProps) => {
|
||||
|
||||
@@ -21,7 +21,7 @@ import { useTheme, useMediaQuery } from '@material-ui/core';
|
||||
|
||||
import { BackstageTheme } from '@backstage/theme';
|
||||
import { CompoundEntityRef } from '@backstage/catalog-model';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { useAnalytics, useApi } from '@backstage/core-plugin-api';
|
||||
import { scmIntegrationsApiRef } from '@backstage/integration-react';
|
||||
|
||||
import {
|
||||
@@ -63,6 +63,7 @@ export const useTechDocsReaderDom = (
|
||||
const isMobileMedia = useMediaQuery(MOBILE_MEDIA_QUERY);
|
||||
const sanitizerTransformer = useSanitizerTransformer();
|
||||
const stylesTransformer = useStylesTransformer();
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const techdocsStorageApi = useApi(techdocsStorageApiRef);
|
||||
const scmIntegrationsApi = useApi(scmIntegrationsApiRef);
|
||||
@@ -177,6 +178,12 @@ export const useTechDocsReaderDom = (
|
||||
const parsedUrl = new URL(url);
|
||||
const fullPath = `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`;
|
||||
|
||||
// capture link clicks within documentation
|
||||
const linkText =
|
||||
(event.target as HTMLAnchorElement | undefined)?.innerText || url;
|
||||
const to = url.replace(window.location.origin, '');
|
||||
analytics.captureEvent('click', linkText, { attributes: { to } });
|
||||
|
||||
// hash exists when anchor is clicked on secondary sidebar
|
||||
if (parsedUrl.hash) {
|
||||
if (modifierActive) {
|
||||
@@ -219,7 +226,7 @@ export const useTechDocsReaderDom = (
|
||||
onLoaded: () => {},
|
||||
}),
|
||||
]),
|
||||
[theme, navigate],
|
||||
[theme, navigate, analytics],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user