Merge pull request #14573 from backstage/techdocs/entity-ref-analytics

[TechDocs] Analytics entity-awareness
This commit is contained in:
Eric Peterson
2022-11-14 12:21:24 +01:00
committed by GitHub
5 changed files with 61 additions and 8 deletions
+5
View File
@@ -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.
+5
View File
@@ -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.
+30 -2
View File
@@ -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',
},
});
});
});
+12 -4
View File
@@ -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(() => {