diff --git a/.changeset/quiet-parrots-occur.md b/.changeset/quiet-parrots-occur.md
new file mode 100644
index 0000000000..c9a6ce9a1d
--- /dev/null
+++ b/.changeset/quiet-parrots-occur.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-graph': patch
+---
+
+Capture analytics events for clicks in the graph.
diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx
index 7e3149fef7..9fe482a30b 100644
--- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx
+++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx
@@ -15,12 +15,14 @@
*/
import { Entity } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { analyticsApiRef } from '@backstage/core-plugin-api';
import {
CatalogApi,
catalogApiRef,
EntityProvider,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { MockAnalyticsApi, renderInTestApp } from '@backstage/test-utils';
+import userEvent from '@testing-library/user-event';
import React from 'react';
import { catalogEntityRouteRef, catalogGraphRouteRef } from '../../routes';
import { CatalogGraphCard } from './CatalogGraphCard';
@@ -117,4 +119,30 @@ describe('', () => {
'/catalog-graph?rootEntityRefs%5B%5D=b%3Ad%2Fc',
);
});
+
+ test('captures analytics event on click', async () => {
+ const analyticsSpy = new MockAnalyticsApi();
+ const { findByText } = await renderInTestApp(
+
+ {wrapper}
+ ,
+ {
+ mountedRoutes: {
+ '/entity/{kind}/{namespace}/{name}': catalogEntityRouteRef,
+ '/catalog-graph': catalogGraphRouteRef,
+ },
+ },
+ );
+
+ expect(await findByText('b:d/c')).toBeInTheDocument();
+ userEvent.click(await findByText('b:d/c'));
+
+ expect(analyticsSpy.getEvents()[0]).toMatchObject({
+ action: 'click',
+ subject: 'b:d/c',
+ attributes: {
+ to: '/entity/{kind}/{namespace}/{name}',
+ },
+ });
+ });
});
diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx
index 5ac8a89539..42e85a75ba 100644
--- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx
+++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.tsx
@@ -19,8 +19,11 @@ import {
stringifyEntityRef,
} from '@backstage/catalog-model';
import { InfoCard, InfoCardVariants } from '@backstage/core-components';
-import { useRouteRef } from '@backstage/core-plugin-api';
-import { useEntity } from '@backstage/plugin-catalog-react';
+import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api';
+import {
+ formatEntityRefTitle,
+ useEntity,
+} from '@backstage/plugin-catalog-react';
import { makeStyles, Theme } from '@material-ui/core';
import qs from 'qs';
import React, { MouseEvent, useCallback } from 'react';
@@ -78,6 +81,7 @@ export const CatalogGraphCard = ({
const catalogGraphRoute = useRouteRef(catalogGraphRouteRef);
const navigate = useNavigate();
const classes = useStyles({ height });
+ const analytics = useAnalytics();
const onNodeClick = useCallback(
(node: EntityNode, _: MouseEvent) => {
@@ -87,9 +91,14 @@ export const CatalogGraphCard = ({
namespace: nodeEntityName.namespace.toLocaleLowerCase('en-US'),
name: nodeEntityName.name,
});
+ analytics.captureEvent(
+ 'click',
+ node.title ?? formatEntityRefTitle(nodeEntityName),
+ { attributes: { to: path } },
+ );
navigate(path);
},
- [catalogEntityRoute, navigate],
+ [catalogEntityRoute, navigate, analytics],
);
const catalogGraphParams = qs.stringify(
diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx
index 8b467a017e..b992d0c976 100644
--- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx
+++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx
@@ -15,8 +15,9 @@
*/
import { RELATION_HAS_PART, RELATION_PART_OF } from '@backstage/catalog-model';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { analyticsApiRef } from '@backstage/core-plugin-api';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { MockAnalyticsApi, renderInTestApp } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { catalogEntityRouteRef } from '../../routes';
@@ -167,4 +168,53 @@ describe('', () => {
expect(navigate).toBeCalledWith('/entity/{kind}/{namespace}/{name}');
});
+
+ test('should capture analytics event when selecting other entity', async () => {
+ const analyticsSpy = new MockAnalyticsApi();
+ const { getByText, findAllByTestId } = await renderInTestApp(
+
+ {wrapper}
+ ,
+ {
+ mountedRoutes: {
+ '/entity/{kind}/{namespace}/{name}': catalogEntityRouteRef,
+ },
+ },
+ );
+
+ expect(await findAllByTestId('node')).toHaveLength(2);
+
+ userEvent.click(getByText('b:d/e'));
+
+ expect(analyticsSpy.getEvents()[0]).toMatchObject({
+ action: 'click',
+ subject: 'b:d/e',
+ });
+ });
+
+ test('should capture analytics event when navigating to entity', async () => {
+ const analyticsSpy = new MockAnalyticsApi();
+ const { getByText, findAllByTestId } = await renderInTestApp(
+
+ {wrapper}
+ ,
+ {
+ mountedRoutes: {
+ '/entity/{kind}/{namespace}/{name}': catalogEntityRouteRef,
+ },
+ },
+ );
+
+ expect(await findAllByTestId('node')).toHaveLength(2);
+
+ userEvent.click(getByText('b:d/e'), { shiftKey: true });
+
+ expect(analyticsSpy.getEvents()[0]).toMatchObject({
+ action: 'click',
+ subject: 'b:d/e',
+ attributes: {
+ to: '/entity/{kind}/{namespace}/{name}',
+ },
+ });
+ });
});
diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx
index f5f1ced541..fdcb71d3ad 100644
--- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx
+++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx
@@ -21,7 +21,7 @@ import {
Page,
SupportButton,
} from '@backstage/core-components';
-import { useRouteRef } from '@backstage/core-plugin-api';
+import { useAnalytics, useRouteRef } from '@backstage/core-plugin-api';
import { formatEntityRefTitle } from '@backstage/plugin-catalog-react';
import { Grid, makeStyles, Paper, Typography } from '@material-ui/core';
import FilterListIcon from '@material-ui/icons/FilterList';
@@ -31,11 +31,11 @@ import React, { MouseEvent, useCallback } from 'react';
import { useNavigate } from 'react-router';
import { catalogEntityRouteRef } from '../../routes';
import {
+ ALL_RELATION_PAIRS,
Direction,
EntityNode,
EntityRelationsGraph,
RelationPairs,
- ALL_RELATION_PAIRS,
} from '../EntityRelationsGraph';
import { DirectionFilter } from './DirectionFilter';
import { MaxDepthFilter } from './MaxDepthFilter';
@@ -133,6 +133,7 @@ export const CatalogGraphPage = ({
showFilters,
toggleShowFilters,
} = useCatalogGraphPage({ initialState });
+ const analytics = useAnalytics();
const onNodeClick = useCallback(
(node: EntityNode, event: MouseEvent) => {
const nodeEntityName = parseEntityRef(node.id);
@@ -143,12 +144,22 @@ export const CatalogGraphPage = ({
namespace: nodeEntityName.namespace.toLocaleLowerCase('en-US'),
name: nodeEntityName.name,
});
+
+ analytics.captureEvent(
+ 'click',
+ node.title ?? formatEntityRefTitle(nodeEntityName),
+ { attributes: { to: path } },
+ );
navigate(path);
} else {
+ analytics.captureEvent(
+ 'click',
+ node.title ?? formatEntityRefTitle(nodeEntityName),
+ );
setRootEntityNames([nodeEntityName]);
}
},
- [catalogEntityRoute, navigate, setRootEntityNames],
+ [catalogEntityRoute, navigate, setRootEntityNames, analytics],
);
return (