feat(core-component): capture sidebar item clicks
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -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(
|
||||
<Sidebar>
|
||||
<SidebarSearchField onSearch={() => {}} to="/search" />
|
||||
<SidebarItem text="Home" icon={HomeIcon} to="./" />
|
||||
<SidebarItem
|
||||
icon={CreateComponentIcon}
|
||||
onClick={() => {}}
|
||||
text="Create..."
|
||||
className={result.current.spotlight}
|
||||
/>
|
||||
<SidebarExpandButton />
|
||||
</Sidebar>,
|
||||
<TestApiProvider apis={[[analyticsApiRef, analyticsApiMock]]}>
|
||||
<Sidebar>
|
||||
<SidebarSearchField onSearch={() => {}} to="/search" />
|
||||
<SidebarItem text="Home" icon={HomeIcon} to="./" />
|
||||
<SidebarItem
|
||||
icon={CreateComponentIcon}
|
||||
onClick={handleSidebarItemClick}
|
||||
text="Create..."
|
||||
className={result.current.spotlight}
|
||||
/>
|
||||
<SidebarItem
|
||||
icon={CreateComponentIcon}
|
||||
to="/docs"
|
||||
onClick={handleSidebarItemClick}
|
||||
text="Docs"
|
||||
className={result.current.spotlight}
|
||||
/>
|
||||
<SidebarItem
|
||||
icon={CreateComponentIcon}
|
||||
to="/explore"
|
||||
onClick={handleSidebarItemClick}
|
||||
text="Explore"
|
||||
className={result.current.spotlight}
|
||||
noTrack
|
||||
/>
|
||||
<SidebarExpandButton />
|
||||
</Sidebar>
|
||||
</TestApiProvider>,
|
||||
);
|
||||
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 () => {
|
||||
|
||||
@@ -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<any, SidebarItemProps>((props, ref) => {
|
||||
hasSubmenu = false,
|
||||
disableHighlight = false,
|
||||
onClick,
|
||||
noTrack,
|
||||
children,
|
||||
className,
|
||||
...navLinkProps
|
||||
@@ -424,9 +432,33 @@ const SidebarItemBase = forwardRef<any, SidebarItemProps>((props, ref) => {
|
||||
),
|
||||
};
|
||||
|
||||
const analyticsApi = useAnalytics();
|
||||
const { pathname: to } = useResolvedPath(
|
||||
!isButtonItem(props) && props.to ? props.to : '',
|
||||
);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>) => {
|
||||
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 (
|
||||
<Button role="button" aria-label={text} {...childProps} ref={ref}>
|
||||
<Button
|
||||
role="button"
|
||||
aria-label={text}
|
||||
{...childProps}
|
||||
ref={ref}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{content}
|
||||
</Button>
|
||||
);
|
||||
@@ -440,6 +472,7 @@ const SidebarItemBase = forwardRef<any, SidebarItemProps>((props, ref) => {
|
||||
ref={ref}
|
||||
aria-label={text ? text : props.to}
|
||||
{...navLinkProps}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{content}
|
||||
</WorkaroundNavLink>
|
||||
|
||||
Reference in New Issue
Block a user