Skip to content

Commit f85f637

Browse files
authored
chore(compass-telemetry): add telemetry events for Compass COMPASS-9660 (#7168)
1 parent 59ffc14 commit f85f637

File tree

21 files changed

+432
-225
lines changed

21 files changed

+432
-225
lines changed

configs/eslint-config-compass/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ const tsxRules = {
4444
'react-hooks/exhaustive-deps': [
4545
'warn',
4646
{
47-
additionalHooks:
48-
'(useTrackOnChange|useContextMenuItems|useContextMenuGroups)',
47+
additionalHooks: '(useTrackOnChange|useContextMenuGroups)',
4948
},
5049
],
5150
};

packages/compass-components/src/components/compass-components-provider.tsx

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import { GuideCueProvider } from './guide-cue/guide-cue';
66
import { SignalHooksProvider } from './signal-popover';
77
import { RequiredURLSearchParamsProvider } from './links/link';
88
import { StackedComponentProvider } from '../hooks/use-stacked-component';
9-
import { ContextMenuProvider } from './context-menu';
9+
import {
10+
type ContextMenuItem,
11+
type ContextMenuItemGroup,
12+
ContextMenuProvider,
13+
} from './context-menu';
1014
import { DrawerContentProvider } from './drawer-portal';
1115

1216
type GuideCueProviderProps = React.ComponentProps<typeof GuideCueProvider>;
@@ -46,6 +50,17 @@ type CompassComponentsProviderProps = {
4650
} & {
4751
utmSource?: string;
4852
utmMedium?: string;
53+
/**
54+
* Callback for when the context menu is opened.
55+
*/
56+
onContextMenuOpen?: (itemGroups: ContextMenuItemGroup[]) => void;
57+
/**
58+
* Callback for when a context menu item is clicked.
59+
*/
60+
onContextMenuItemClick?: (
61+
itemGroup: ContextMenuItemGroup,
62+
item: ContextMenuItem
63+
) => void;
4964
} & React.ComponentProps<typeof SignalHooksProvider>;
5065

5166
const darkModeMediaQuery = (() => {
@@ -102,6 +117,8 @@ export const CompassComponentsProvider = ({
102117
children,
103118
onNextGuideGue,
104119
onNextGuideCueGroup,
120+
onContextMenuOpen,
121+
onContextMenuItemClick,
105122
utmSource,
106123
utmMedium,
107124
stackedElementsZIndex,
@@ -144,7 +161,11 @@ export const CompassComponentsProvider = ({
144161
>
145162
<SignalHooksProvider {...signalHooksProviderProps}>
146163
<ConfirmationModalArea>
147-
<ContextMenuProvider disabled={disableContextMenus}>
164+
<ContextMenuProvider
165+
disabled={disableContextMenus}
166+
onContextMenuOpen={onContextMenuOpen}
167+
onContextMenuItemClick={onContextMenuItemClick}
168+
>
148169
<ToastArea>
149170
{typeof children === 'function'
150171
? children({

packages/compass-components/src/components/context-menu.spec.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { expect } from 'chai';
44
import sinon from 'sinon';
55
import { ContextMenuProvider } from '@mongodb-js/compass-context-menu';
66
import {
7-
useContextMenuItems,
87
ContextMenu,
98
type ContextMenuItem,
9+
useContextMenuGroups,
1010
} from './context-menu';
1111

12-
describe('useContextMenuItems', function () {
12+
describe('useContextMenuGroups', function () {
1313
const menuTestTriggerId = 'test-trigger';
1414

1515
const TestComponent = ({
@@ -21,7 +21,15 @@ describe('useContextMenuItems', function () {
2121
children?: React.ReactNode;
2222
'data-testid'?: string;
2323
}) => {
24-
const ref = useContextMenuItems(() => items, [items]);
24+
const ref = useContextMenuGroups(
25+
() => [
26+
{
27+
telemetryLabel: 'Test Item Group',
28+
items,
29+
},
30+
],
31+
[items]
32+
);
2533

2634
return (
2735
<div data-testid={dataTestId} ref={ref}>

packages/compass-components/src/components/context-menu.tsx

Lines changed: 79 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,42 @@ const itemStyles = css({
3333
export function ContextMenuProvider({
3434
children,
3535
disabled,
36+
onContextMenuItemClick,
37+
onContextMenuOpen,
3638
}: {
3739
children: React.ReactNode;
3840
disabled?: boolean;
41+
onContextMenuOpen?: (itemGroups: ContextMenuItemGroup[]) => void;
42+
onContextMenuItemClick?: (
43+
itemGroup: ContextMenuItemGroup,
44+
item: ContextMenuItem
45+
) => void;
3946
}) {
4047
return (
41-
<ContextMenuProviderBase disabled={disabled} menuWrapper={ContextMenu}>
48+
<ContextMenuProviderBase
49+
disabled={disabled}
50+
menuWrapper={(props) => (
51+
<ContextMenu
52+
{...props}
53+
onContextMenuItemClick={onContextMenuItemClick}
54+
/>
55+
)}
56+
onContextMenuOpen={onContextMenuOpen}
57+
>
4258
{children}
4359
</ContextMenuProviderBase>
4460
);
4561
}
4662

47-
export function ContextMenu({ menu }: ContextMenuWrapperProps) {
63+
export function ContextMenu({
64+
menu,
65+
onContextMenuItemClick,
66+
}: ContextMenuWrapperProps & {
67+
onContextMenuItemClick?: (
68+
itemGroup: ContextMenuItemGroup,
69+
item: ContextMenuItem
70+
) => void;
71+
}) {
4872
const menuRef = useRef(null);
4973
const anchorRef = useRef<HTMLDivElement>(null);
5074

@@ -80,79 +104,79 @@ export function ContextMenu({ menu }: ContextMenuWrapperProps) {
80104
className={cx(menuStyles, contextMenuClassName)}
81105
maxHeight={Number.MAX_SAFE_INTEGER}
82106
>
83-
{itemGroups.map((items: ContextMenuItemGroup, groupIndex: number) => {
84-
return (
85-
<div
86-
key={`menu-group-${groupIndex}`}
87-
data-testid={`menu-group-${groupIndex}`}
88-
>
89-
{items.map((item: ContextMenuItem, itemIndex: number) => {
90-
return (
91-
<MenuItem
92-
key={`menu-group-${groupIndex}-item-${itemIndex}`}
93-
data-text={item.label}
94-
data-testid={`menu-group-${groupIndex}-item-${itemIndex}`}
95-
className={itemStyles}
96-
onClick={(evt: React.MouseEvent) => {
97-
item.onAction?.(evt);
98-
menu.close();
99-
}}
107+
{itemGroups.map(
108+
(itemGroup: ContextMenuItemGroup, groupIndex: number) => {
109+
return (
110+
<div
111+
key={`menu-group-${groupIndex}`}
112+
data-testid={`menu-group-${groupIndex}`}
113+
>
114+
{itemGroup.items.map(
115+
(item: ContextMenuItem, itemIndex: number) => {
116+
return (
117+
<MenuItem
118+
key={`menu-group-${groupIndex}-item-${itemIndex}`}
119+
data-text={item.label}
120+
data-testid={`menu-group-${groupIndex}-item-${itemIndex}`}
121+
className={itemStyles}
122+
onClick={(evt: React.MouseEvent) => {
123+
item.onAction?.(evt);
124+
onContextMenuItemClick?.(itemGroup, item);
125+
menu.close();
126+
}}
127+
>
128+
{item.label}
129+
</MenuItem>
130+
);
131+
}
132+
)}
133+
{groupIndex < itemGroups.length - 1 && (
134+
<div
135+
key={`menu-group-${groupIndex}-separator`}
136+
data-testid={`menu-group-${groupIndex}-separator`}
100137
>
101-
{item.label}
102-
</MenuItem>
103-
);
104-
})}
105-
{groupIndex < itemGroups.length - 1 && (
106-
<div
107-
key={`menu-group-${groupIndex}-separator`}
108-
data-testid={`menu-group-${groupIndex}-separator`}
109-
>
110-
<MenuSeparator />
111-
</div>
112-
)}
113-
</div>
114-
);
115-
})}
138+
<MenuSeparator />
139+
</div>
140+
)}
141+
</div>
142+
);
143+
}
144+
)}
116145
</Menu>
117146
</div>
118147
);
119148
}
120149

121-
/** Registers context menu items - items that are `undefined` will get filtered. */
122-
export function useContextMenuItems(
123-
getItems: () => (ContextMenuItem | undefined)[],
124-
dependencies: React.DependencyList | undefined
125-
): React.RefCallback<HTMLElement> {
126-
const memoizedItems = useMemo(
127-
() =>
128-
getItems().filter((item): item is ContextMenuItem => item !== undefined),
129-
// eslint-disable-next-line react-hooks/exhaustive-deps
130-
dependencies
131-
);
132-
const contextMenu = useContextMenu();
133-
return contextMenu.registerItems(memoizedItems);
134-
}
135-
136150
/** Registers context menu groups - groups and items that are `undefined` will get filtered. */
137151
export function useContextMenuGroups(
138-
getGroups: () => ((ContextMenuItem | undefined)[] | undefined)[],
152+
getGroups: () => (
153+
| (Omit<ContextMenuItemGroup, 'items'> & {
154+
items: undefined | (ContextMenuItem | undefined)[];
155+
})
156+
| undefined
157+
)[],
139158
dependencies: React.DependencyList | undefined
140159
): React.RefCallback<HTMLElement> {
141-
const memoizedGroups: ContextMenuItem[][] = useMemo(
160+
const memoizedGroups: ContextMenuItemGroup[] = useMemo(
142161
() => {
143162
const groups = getGroups();
144163
// Cleanup all undefined fields across items and groups which is used
145164
// for conditional displaying of groups and items.
146165
return groups
147166
.filter(
148-
(groupItems): groupItems is ContextMenuItem[] =>
149-
groupItems !== undefined && groupItems.length > 0
167+
(group): group is ContextMenuItemGroup =>
168+
group !== undefined &&
169+
group.items !== undefined &&
170+
group.items.length > 0
150171
)
151-
.map((groupItems) => groupItems.filter((item) => item !== undefined));
172+
.map(({ items, telemetryLabel }) => ({
173+
items: items?.filter((item) => item !== undefined),
174+
telemetryLabel,
175+
}));
152176
},
153177
// eslint-disable-next-line react-hooks/exhaustive-deps
154178
dependencies
155179
);
156180
const contextMenu = useContextMenu();
157-
return contextMenu.registerItems(...memoizedGroups);
181+
return contextMenu.registerItemGroups(memoizedGroups);
158182
}

packages/compass-components/src/components/document-list/element.tsx

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import { palette } from '@leafygreen-ui/palette';
2828
import { Icon } from '../leafygreen';
2929
import { useDarkMode } from '../../hooks/use-theme';
3030
import VisibleFieldsToggle from './visible-field-toggle';
31-
import { useContextMenuItems } from '../context-menu';
3231
import { hasDistinctValue } from 'mongodb-query-util';
32+
import { useContextMenuGroups } from '../context-menu';
3333

3434
function getEditorByType(type: HadronElementType['type']) {
3535
switch (type) {
@@ -499,40 +499,45 @@ export const HadronElement: React.FunctionComponent<{
499499
);
500500

501501
// Add context menu hook for the field
502-
const fieldContextMenuRef = useContextMenuItems(
502+
const fieldContextMenuRef = useContextMenuGroups(
503503
() => [
504-
onUpdateQuery
505-
? {
506-
label: isFieldInQuery(
507-
getNestedKeyPathForElement(element),
508-
element.generateObject()
509-
)
510-
? 'Remove from query'
511-
: 'Add to query',
504+
{
505+
telemetryLabel: 'Element Field',
506+
items: [
507+
onUpdateQuery
508+
? {
509+
label: isFieldInQuery(
510+
getNestedKeyPathForElement(element),
511+
element.generateObject()
512+
)
513+
? 'Remove from query'
514+
: 'Add to query',
515+
onAction: () => {
516+
onUpdateQuery(
517+
getNestedKeyPathForElement(element),
518+
element.generateObject()
519+
);
520+
},
521+
}
522+
: undefined,
523+
{
524+
label: 'Copy field & value',
512525
onAction: () => {
513-
onUpdateQuery(
514-
getNestedKeyPathForElement(element),
515-
element.generateObject()
526+
void navigator.clipboard.writeText(
527+
`${key.value}: ${element.toEJSON()}`
516528
);
517529
},
518-
}
519-
: undefined,
520-
{
521-
label: 'Copy field & value',
522-
onAction: () => {
523-
void navigator.clipboard.writeText(
524-
`${key.value}: ${element.toEJSON()}`
525-
);
526-
},
530+
},
531+
type.value === 'String' && isValidUrl(value.value)
532+
? {
533+
label: 'Open URL in browser',
534+
onAction: () => {
535+
window.open(value.value, '_blank', 'noopener');
536+
},
537+
}
538+
: undefined,
539+
],
527540
},
528-
type.value === 'String' && isValidUrl(value.value)
529-
? {
530-
label: 'Open URL in browser',
531-
onAction: () => {
532-
window.open(value.value, '_blank', 'noopener');
533-
},
534-
}
535-
: undefined,
536541
],
537542
[element, key.value, value.value, type.value, onUpdateQuery, isFieldInQuery]
538543
);

packages/compass-components/src/components/workspace-tabs/tab.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { LogoIcon } from '../icons/logo-icon';
1414
import { Tooltip } from '../leafygreen';
1515
import { ServerIcon } from '../icons/server-icon';
1616
import { useTabTheme } from './use-tab-theme';
17-
import { useContextMenuItems } from '../context-menu';
17+
import { useContextMenuGroups } from '../context-menu';
1818

1919
function focusedChild(className: string) {
2020
return `&:hover ${className}, &:focus-visible ${className}, &:focus-within:not(:focus) ${className}`;
@@ -239,10 +239,15 @@ function Tab({
239239
return css(tabTheme);
240240
}, [tabTheme, darkMode]);
241241

242-
const contextMenuRef = useContextMenuItems(
242+
const contextMenuRef = useContextMenuGroups(
243243
() => [
244-
{ label: 'Close all other tabs', onAction: onCloseAllOthers },
245-
{ label: 'Duplicate', onAction: onDuplicate },
244+
{
245+
telemetryLabel: 'Workspace Tab',
246+
items: [
247+
{ label: 'Close all other tabs', onAction: onCloseAllOthers },
248+
{ label: 'Duplicate', onAction: onDuplicate },
249+
],
250+
},
246251
],
247252
[onCloseAllOthers, onDuplicate]
248253
);

packages/compass-components/src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ export { FormModal } from './components/modals/form-modal';
103103
export { InfoModal } from './components/modals/info-modal';
104104

105105
export {
106-
useContextMenuItems,
107106
useContextMenuGroups,
108107
type ContextMenuItem,
109108
type ContextMenuItemGroup,

0 commit comments

Comments
 (0)