From 0c17901e457679857cdc1c1e2cbf411cbc37d40a Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 14 Oct 2021 09:26:45 +0200 Subject: [PATCH 1/2] Fix typo in docs Signed-off-by: Oliver Sand --- docs/plugins/analytics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index 0b3d5dfd9c..7adf2650df 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -287,7 +287,7 @@ describe('SomeComponent', () => { await waitFor(() => { expect(apiSpy.getEvents()[0]).toMatchObject({ action: 'expected action', - subject: 'expected subject'', + subject: 'expected subject', attributes: { foo: 'bar', }, From ce0aef18417a4df705470fde41cc26064f3bf1b2 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Thu, 14 Oct 2021 09:43:13 +0200 Subject: [PATCH 2/2] Capture analytics events for clicks in the graph Signed-off-by: Oliver Sand --- .changeset/quiet-parrots-occur.md | 5 ++ .../CatalogGraphCard.test.tsx | 30 ++++++++++- .../CatalogGraphCard/CatalogGraphCard.tsx | 15 ++++-- .../CatalogGraphPage.test.tsx | 52 ++++++++++++++++++- .../CatalogGraphPage/CatalogGraphPage.tsx | 17 ++++-- 5 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 .changeset/quiet-parrots-occur.md 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 (