Skip to content

Commit 985f81f

Browse files
committed
chore(compass-telemetry): add telemetry events for Compass
1 parent a502609 commit 985f81f

File tree

20 files changed

+421
-230
lines changed

20 files changed

+421
-230
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
@@ -32,18 +32,42 @@ const itemStyles = css({
3232
export function ContextMenuProvider({
3333
children,
3434
disabled,
35+
onContextMenuItemClick,
36+
onContextMenuOpen,
3537
}: {
3638
children: React.ReactNode;
3739
disabled?: boolean;
40+
onContextMenuOpen?: (itemGroups: ContextMenuItemGroup[]) => void;
41+
onContextMenuItemClick?: (
42+
itemGroup: ContextMenuItemGroup,
43+
item: ContextMenuItem
44+
) => void;
3845
}) {
3946
return (
40-
<ContextMenuProviderBase disabled={disabled} menuWrapper={ContextMenu}>
47+
<ContextMenuProviderBase
48+
disabled={disabled}
49+
menuWrapper={(props) => (
50+
<ContextMenu
51+
{...props}
52+
onContextMenuItemClick={onContextMenuItemClick}
53+
/>
54+
)}
55+
onContextMenuOpen={onContextMenuOpen}
56+
>
4157
{children}
4258
</ContextMenuProviderBase>
4359
);
4460
}
4561

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

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

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

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)