Merge pull request #7598 from SDA-SE/feat/graph-analytics

Catalog graph analytics
This commit is contained in:
Oliver Sand
2021-10-18 20:44:46 +02:00
committed by GitHub
6 changed files with 112 additions and 9 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-graph': patch
---
Capture analytics events for clicks in the graph.
+1 -1
View File
@@ -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',
},
@@ -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('<CatalogGraphCard/>', () => {
'/catalog-graph?rootEntityRefs%5B%5D=b%3Ad%2Fc',
);
});
test('captures analytics event on click', async () => {
const analyticsSpy = new MockAnalyticsApi();
const { findByText } = await renderInTestApp(
<ApiProvider apis={ApiRegistry.from([[analyticsApiRef, analyticsSpy]])}>
{wrapper}
</ApiProvider>,
{
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}',
},
});
});
});
@@ -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<unknown>) => {
@@ -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(
@@ -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('<CatalogGraphPage/>', () => {
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(
<ApiProvider apis={ApiRegistry.from([[analyticsApiRef, analyticsSpy]])}>
{wrapper}
</ApiProvider>,
{
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(
<ApiProvider apis={ApiRegistry.from([[analyticsApiRef, analyticsSpy]])}>
{wrapper}
</ApiProvider>,
{
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}',
},
});
});
});
@@ -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<unknown>) => {
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 (