diff --git a/packages/core-components/src/layout/Sidebar/Items.test.tsx b/packages/core-components/src/layout/Sidebar/Items.test.tsx
index eafbf3827e..9479180180 100644
--- a/packages/core-components/src/layout/Sidebar/Items.test.tsx
+++ b/packages/core-components/src/layout/Sidebar/Items.test.tsx
@@ -15,7 +15,11 @@
*/
import React from 'react';
-import { renderInTestApp } from '@backstage/test-utils';
+import {
+ MockAnalyticsApi,
+ TestApiProvider,
+ renderInTestApp,
+} from '@backstage/test-utils';
import { createEvent, fireEvent, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import HomeIcon from '@material-ui/icons/Home';
@@ -24,6 +28,7 @@ import { Sidebar } from './Bar';
import { SidebarItem, SidebarSearchField, SidebarExpandButton } from './Items';
import { renderHook } from '@testing-library/react-hooks';
import { makeStyles } from '@material-ui/core/styles';
+import { analyticsApiRef } from '@backstage/core-plugin-api';
const useStyles = makeStyles({
spotlight: {
@@ -31,27 +36,50 @@ const useStyles = makeStyles({
},
});
+let analyticsApiMock: MockAnalyticsApi;
+
+const handleSidebarItemClick = jest.fn();
+
async function renderSidebar() {
const { result } = renderHook(() => useStyles());
await renderInTestApp(
-
- {}} to="/search" />
-
- {}}
- text="Create..."
- className={result.current.spotlight}
- />
-
- ,
+
+
+ {}} to="/search" />
+
+
+
+
+
+
+ ,
);
await userEvent.hover(screen.getByTestId('sidebar-root'));
}
describe('Items', () => {
beforeEach(async () => {
+ jest.clearAllMocks();
+ analyticsApiMock = new MockAnalyticsApi();
await renderSidebar();
});
@@ -73,6 +101,40 @@ describe('Items', () => {
await screen.findByRole('button', { name: /create/i }),
).toHaveStyle(`background-color: transparent`);
});
+
+ it('should send button clicks to analytics', async () => {
+ await userEvent.click(
+ await screen.findByRole('button', { name: /create/i }),
+ );
+ expect(handleSidebarItemClick).toHaveBeenCalledTimes(1);
+ expect(analyticsApiMock.getEvents()).toHaveLength(1);
+ expect(analyticsApiMock.getEvents()[0]).toMatchObject({
+ action: 'click',
+ subject: 'Create...',
+ context: { routeRef: 'unknown', pluginId: 'root', extension: 'App' },
+ attributes: { to: '/' },
+ });
+ });
+
+ it('should send link clicks to analytics', async () => {
+ await userEvent.click(await screen.findByRole('link', { name: /docs/i }));
+ expect(handleSidebarItemClick).toHaveBeenCalledTimes(1);
+ expect(analyticsApiMock.getEvents()).toHaveLength(1);
+ expect(analyticsApiMock.getEvents()[0]).toMatchObject({
+ action: 'click',
+ subject: 'Docs',
+ context: { routeRef: 'unknown', pluginId: 'root', extension: 'App' },
+ attributes: { to: '/docs' },
+ });
+ });
+
+ it('should not send clicks to analytics when tracking is disabled', async () => {
+ await userEvent.click(
+ await screen.findByRole('link', { name: /explore/i }),
+ );
+ expect(handleSidebarItemClick).toHaveBeenCalledTimes(1);
+ expect(analyticsApiMock.getEvents()).toHaveLength(0);
+ });
});
describe('SidebarSearchField', () => {
it('should be defaultPrevented when enter is pressed', async () => {
diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx
index 00d1871947..96c2b382e7 100644
--- a/packages/core-components/src/layout/Sidebar/Items.tsx
+++ b/packages/core-components/src/layout/Sidebar/Items.tsx
@@ -13,7 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { IconComponent, useElementFilter } from '@backstage/core-plugin-api';
+import {
+ IconComponent,
+ useAnalytics,
+ useElementFilter,
+} from '@backstage/core-plugin-api';
import { BackstageTheme } from '@backstage/theme';
import Badge from '@material-ui/core/Badge';
import Box from '@material-ui/core/Box';
@@ -38,6 +42,7 @@ import React, {
forwardRef,
KeyboardEventHandler,
ReactNode,
+ useCallback,
useContext,
useMemo,
useState,
@@ -264,6 +269,8 @@ type SidebarItemBaseProps = {
hasSubmenu?: boolean;
disableHighlight?: boolean;
className?: string;
+ noTrack?: boolean;
+ onClick?: (ev: React.MouseEvent) => void;
};
type SidebarItemButtonProps = SidebarItemBaseProps & {
@@ -362,6 +369,7 @@ const SidebarItemBase = forwardRef((props, ref) => {
hasSubmenu = false,
disableHighlight = false,
onClick,
+ noTrack,
children,
className,
...navLinkProps
@@ -424,9 +432,33 @@ const SidebarItemBase = forwardRef((props, ref) => {
),
};
+ const analyticsApi = useAnalytics();
+ const { pathname: to } = useResolvedPath(
+ !isButtonItem(props) && props.to ? props.to : '',
+ );
+
+ const handleClick = useCallback(
+ (event: React.MouseEvent) => {
+ if (!noTrack) {
+ const action = 'click';
+ const subject = text ?? 'Sidebar Item';
+ const options = to ? { attributes: { to } } : undefined;
+ analyticsApi.captureEvent(action, subject, options);
+ }
+ onClick?.(event);
+ },
+ [analyticsApi, text, to, noTrack, onClick],
+ );
+
if (isButtonItem(props)) {
return (
-