diff --git a/extensions/kube-object-event-status/package.json b/extensions/kube-object-event-status/package.json index a453097288..5673651edc 100644 --- a/extensions/kube-object-event-status/package.json +++ b/extensions/kube-object-event-status/package.json @@ -1,6 +1,6 @@ { "name": "kube-object-event-status", - "version": "0.0.1", + "version": "5.5.0-alpha.0.1651664036833", "description": "Adds kube object status from events", "renderer": "dist/renderer.js", "lens": { diff --git a/extensions/metrics-cluster-feature/package.json b/extensions/metrics-cluster-feature/package.json index 708d644b28..41a50b09d8 100644 --- a/extensions/metrics-cluster-feature/package.json +++ b/extensions/metrics-cluster-feature/package.json @@ -1,6 +1,6 @@ { "name": "lens-metrics-cluster-feature", - "version": "0.0.1", + "version": "5.5.0-alpha.0.1651664036833", "description": "Lens metrics cluster feature", "renderer": "dist/renderer.js", "lens": { diff --git a/extensions/node-menu/package.json b/extensions/node-menu/package.json index 4ec9fb5688..4ef84f0be3 100644 --- a/extensions/node-menu/package.json +++ b/extensions/node-menu/package.json @@ -1,6 +1,6 @@ { "name": "lens-node-menu", - "version": "0.0.1", + "version": "5.5.0-alpha.0.1651664036833", "description": "Lens node menu", "renderer": "dist/renderer.js", "lens": { diff --git a/extensions/pod-menu/package.json b/extensions/pod-menu/package.json index c55c9bdd75..424c121b80 100644 --- a/extensions/pod-menu/package.json +++ b/extensions/pod-menu/package.json @@ -1,6 +1,6 @@ { "name": "lens-pod-menu", - "version": "0.0.1", + "version": "5.5.0-alpha.0.1651664036833", "description": "Lens pod menu", "renderer": "dist/renderer.js", "lens": { diff --git a/src/renderer/components/button/button.tsx b/src/renderer/components/button/button.tsx index 756f50a584..06ef563e69 100644 --- a/src/renderer/components/button/button.tsx +++ b/src/renderer/components/button/button.tsx @@ -8,6 +8,11 @@ import type { ButtonHTMLAttributes } from "react"; import React from "react"; import { cssNames } from "../../utils"; import { withTooltip } from "../tooltip"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import trackEventInjectable from "../../telemetry/track-event.injectable"; +interface Dependencies { + track: (e: React.MouseEvent) => void; +} export interface ButtonProps extends ButtonHTMLAttributes { label?: React.ReactNode; @@ -25,10 +30,10 @@ export interface ButtonProps extends ButtonHTMLAttributes { target?: "_blank"; // in case of using @href } -export const Button = withTooltip((props: ButtonProps) => { +const NonInjectedButton = withTooltip((props: ButtonProps & Dependencies) => { const { waiting, label, primary, accent, plain, hidden, active, big, - round, outlined, light, children, ...btnProps + round, outlined, light, children, track, ...btnProps } = props; if (hidden) return null; @@ -37,10 +42,18 @@ export const Button = withTooltip((props: ButtonProps) => { waiting, primary, accent, plain, active, big, round, outlined, light, }); + const onClick = (e: React.MouseEvent) => { + track(e); + + if (btnProps.onClick) { + btnProps.onClick(e); + } + }; + // render as link if (props.href) { return ( - + {label} {children} @@ -49,9 +62,24 @@ export const Button = withTooltip((props: ButtonProps) => { // render as button return ( - ); }); + + +export const Button = withInjectables( + NonInjectedButton, + + { + getProps: (di, props) => ({ + track: di.inject(trackEventInjectable), + ...props, + }), + }, +); diff --git a/src/renderer/components/checkbox/checkbox.tsx b/src/renderer/components/checkbox/checkbox.tsx index ecba55378e..7f92f84719 100644 --- a/src/renderer/components/checkbox/checkbox.tsx +++ b/src/renderer/components/checkbox/checkbox.tsx @@ -7,6 +7,8 @@ import "./checkbox.scss"; import React from "react"; import type { SingleOrMany } from "../../utils"; import { cssNames, noop } from "../../utils"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import trackWithIdInjectable from "../../telemetry/track-with-id.injectable"; export interface CheckboxProps { className?: string; @@ -18,7 +20,11 @@ export interface CheckboxProps { children?: SingleOrMany; } -export function Checkbox({ label, inline, className, value, children, onChange = noop, disabled, ...inputProps }: CheckboxProps) { +interface Dependencies { + captureClick: (id: string, action: string) => void; +} + +function NonInjectedCheckbox({ label, inline, className, value, children, onChange = noop, disabled, captureClick, ...inputProps }: CheckboxProps & Dependencies) { const componentClass = cssNames("Checkbox flex align-center", className, { inline, checked: value, @@ -32,7 +38,13 @@ export function Checkbox({ label, inline, className, value, children, onChange = type="checkbox" checked={value} disabled={disabled} - onChange={event => onChange(event.target.checked, event)} + onChange={event => { + if (label) { + captureClick(`${window.location.pathname} ${label.toString()}`, `Checkbox ${event.target.checked ? "On" : "Off"}`); + } + + onChange(event.target.checked, event); + }} /> {label ? {label} : null} @@ -40,3 +52,14 @@ export function Checkbox({ label, inline, className, value, children, onChange = ); } + +export const Checkbox = withInjectables( + NonInjectedCheckbox, + + { + getProps: (di, props) => ({ + captureClick: di.inject(trackWithIdInjectable), + ...props, + }), + }, +); diff --git a/src/renderer/components/hotbar/hotbar-icon.tsx b/src/renderer/components/hotbar/hotbar-icon.tsx index 3bbe644c88..221e255f9b 100644 --- a/src/renderer/components/hotbar/hotbar-icon.tsx +++ b/src/renderer/components/hotbar/hotbar-icon.tsx @@ -18,6 +18,7 @@ import { Tooltip } from "../tooltip"; import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable"; +import trackWithIdInjectable from "../../../renderer/telemetry/track-with-id.injectable"; export interface HotbarIconProps extends AvatarProps { uid: string; @@ -32,6 +33,7 @@ export interface HotbarIconProps extends AvatarProps { interface Dependencies { normalizeMenuItem: NormalizeCatalogEntityContextMenu; + capture: (id: string, action: string) => void; } const NonInjectedHotbarIcon = observer(({ @@ -41,7 +43,7 @@ const NonInjectedHotbarIcon = observer(({ normalizeMenuItem, ...props }: HotbarIconProps & Dependencies) => { - const { uid, title, src, material, active, className, source, disabled, onMenuOpen, onClick, children, ...rest } = props; + const { uid, title, src, material, active, className, source, disabled, onMenuOpen, onClick, children, capture, ...rest } = props; const id = `hotbarIcon-${uid}`; const [menuOpen, setMenuOpen] = useState(false); @@ -65,8 +67,13 @@ const NonInjectedHotbarIcon = observer(({ disabled={disabled} size={size} src={src} - onClick={(event) => !disabled && onClick?.(event)} - > + onClick={(event) => { + if (disabled) { + return; + } + capture(title, "Click Hotbar Item"); + onClick?.(event); + }}> {material && } {children} @@ -100,5 +107,6 @@ export const HotbarIcon = withInjectables(NonInje getProps: (di, props) => ({ ...props, normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable), + capture: di.inject(trackWithIdInjectable), }), }); diff --git a/src/renderer/components/item-object-list/content.tsx b/src/renderer/components/item-object-list/content.tsx index 8cff8f21bc..54cc06de78 100644 --- a/src/renderer/components/item-object-list/content.tsx +++ b/src/renderer/components/item-object-list/content.tsx @@ -32,6 +32,7 @@ import userStoreInjectable from "../../../common/user-store/user-store.injectabl import pageFiltersStoreInjectable from "./page-filters/store.injectable"; import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable"; import openConfirmDialogInjectable from "../confirm-dialog/open.injectable"; +import trackWithIdInjectable from "../../../renderer/telemetry/track-with-id.injectable"; export interface ItemListLayoutContentProps { getFilters: () => Filter[]; @@ -75,6 +76,7 @@ interface Dependencies { userStore: UserStore; pageFiltersStore: PageFiltersStore; openConfirmDialog: OpenConfirmDialog; + capture: (id: string, action: string) => void; } @observer @@ -110,7 +112,11 @@ class NonInjectedItemListLayoutContent< searchItem={item} sortItem={item} selected={detailsItem && detailsItem.getId() === item.getId()} - onClick={hasDetailsView ? prevDefault(() => onDetails?.(item)) : undefined} + onClick={hasDetailsView ? prevDefault(() => { + this.props.capture(this.props.tableId, "Table Row Click"); + + return onDetails?.(item); + }) : undefined} {...customizeTableRowProps(item)} > {isSelectable && ( @@ -381,5 +387,6 @@ export const ItemListLayoutContent = withInjectables(props: ItemListLayoutContentProps) => React.ReactElement; diff --git a/src/renderer/components/layout/close-button.tsx b/src/renderer/components/layout/close-button.tsx index 6d0d8219a6..023fa2e7ac 100644 --- a/src/renderer/components/layout/close-button.tsx +++ b/src/renderer/components/layout/close-button.tsx @@ -8,13 +8,26 @@ import styles from "./close-button.module.scss"; import type { HTMLAttributes } from "react"; import React from "react"; import { Icon } from "../icon"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import trackWithIdInjectable from "../../telemetry/track-with-id.injectable"; export interface CloseButtonProps extends HTMLAttributes { } -export function CloseButton(props: CloseButtonProps) { +interface Dependencies { + capture: (id: string, action: string) => void; +} + +function NonInjectedCloseButton(props: CloseButtonProps & Dependencies) { + const { capture, ...rest } = props; + return ( -
+
{ + capture(`${window.location.pathname}`, "Close Button Click"); + props?.onClick(e); + }}>
); } + +export const CloseButton = withInjectables( + NonInjectedCloseButton, + + { + getProps: (di, props) => ({ + capture: di.inject(trackWithIdInjectable), + ...props, + }), + }, +); diff --git a/src/renderer/components/layout/sidebar-item.tsx b/src/renderer/components/layout/sidebar-item.tsx index 64a63be8c4..7439255181 100644 --- a/src/renderer/components/layout/sidebar-item.tsx +++ b/src/renderer/components/layout/sidebar-item.tsx @@ -16,9 +16,11 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import type { SidebarStorageState } from "./sidebar-storage/sidebar-storage.injectable"; import sidebarStorageInjectable from "./sidebar-storage/sidebar-storage.injectable"; import type { HierarchicalSidebarItem } from "./sidebar-items.injectable"; +import trackWithIdInjectable from "../../../renderer/telemetry/track-with-id.injectable"; interface Dependencies { sidebarStorage: StorageLayer; + capture: (id: string, action: string) => void; } export interface SidebarItemProps { @@ -96,6 +98,8 @@ class NonInjectedSidebarItem extends React.Component< event.preventDefault(); event.stopPropagation(); + this.props.capture(this.registration.title.toString(), "Click Side Bar Item"); + if (this.isExpandable) { this.toggleExpand(); } else { @@ -125,5 +129,7 @@ export const SidebarItem = withInjectables(NonIn getProps: (di, props) => ({ ...props, sidebarStorage: di.inject(sidebarStorageInjectable), + capture: di.inject(trackWithIdInjectable), }), }); + diff --git a/src/renderer/components/layout/tab-layout-2.tsx b/src/renderer/components/layout/tab-layout-2.tsx index a0348383c1..30d88b256e 100644 --- a/src/renderer/components/layout/tab-layout-2.tsx +++ b/src/renderer/components/layout/tab-layout-2.tsx @@ -11,17 +11,23 @@ import { cssNames } from "../../utils"; import { Tab, Tabs } from "../tabs"; import { ErrorBoundary } from "../error-boundary"; import type { HierarchicalSidebarItem } from "./sidebar-items.injectable"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import trackWithIdInjectable from "../../../renderer/telemetry/track-with-id.injectable"; +interface Dependencies { + captureClick: (id: string, action: string) => void; +} export interface TabLayoutProps { tabs?: HierarchicalSidebarItem[]; children?: React.ReactNode; } -export const TabLayout = observer( +const NonInjectedTabLayout = observer( ({ tabs = [], children, - }: TabLayoutProps) => { + captureClick, + }: TabLayoutProps & Dependencies) => { const hasTabs = tabs.length > 0; return ( @@ -37,7 +43,10 @@ export const TabLayout = observer( return ( { + captureClick(registration.title.toString(), "Tab Click"); + registration.onClick(); + }} key={registration.id} label={registration.title} active={active} @@ -58,3 +67,14 @@ export const TabLayout = observer( ); }, ); + +export const TabLayout = withInjectables( + NonInjectedTabLayout, + + { + getProps: (di, props) => ({ + captureClick: di.inject(trackWithIdInjectable), + ...props, + }), + }, +); diff --git a/src/renderer/components/menu/menu.tsx b/src/renderer/components/menu/menu.tsx index 582ad37af4..13dce97582 100644 --- a/src/renderer/components/menu/menu.tsx +++ b/src/renderer/components/menu/menu.tsx @@ -13,6 +13,8 @@ import { Animate } from "../animate"; import type { IconProps } from "../icon"; import { Icon } from "../icon"; import isEqual from "lodash/isEqual"; +import { withInjectables } from "@ogre-tools/injectable-react"; +import trackWithIdInjectable from "../../../renderer/telemetry/track-with-id.injectable"; export const MenuContext = React.createContext(null); export type MenuContextValue = Menu; @@ -71,7 +73,7 @@ export class Menu extends React.Component { } public opener: HTMLElement | null = null; public elem: HTMLUListElement | null = null; - protected items: { [index: number]: MenuItem } = {}; + protected items: { [index: number]: NonInjectedMenuItem } = {}; public state: State = {}; get isOpen() { @@ -308,7 +310,7 @@ export class Menu extends React.Component { this.elem = elem; } - protected bindItemRef(item: MenuItem, index: number) { + protected bindItemRef(item: NonInjectedMenuItem, index: number) { this.items[index] = item; } @@ -328,7 +330,7 @@ export class Menu extends React.Component { const menuItems = React.Children.toArray(children).map((item, index) => { if (typeof item === "object" && (item as ReactElement).type === MenuItem) { return React.cloneElement(item as ReactElement, { - ref: (item: MenuItem) => this.bindItemRef(item, index), + ref: (item: NonInjectedMenuItem) => this.bindItemRef(item, index), }); } @@ -389,6 +391,9 @@ export function SubMenu(props: Partial) { ); } +interface Dependencies { + captureClick: (id: string, action: string) => void; +} export interface MenuItemProps extends React.HTMLProps { icon?: string | Partial; disabled?: boolean; @@ -401,14 +406,14 @@ const defaultPropsMenuItem: Partial = { onClick: noop, }; -export class MenuItem extends React.Component { +class NonInjectedMenuItem extends React.Component { static defaultProps = defaultPropsMenuItem as object; static contextType = MenuContext; declare context: MenuContextValue; public elem: HTMLElement | null = null; - constructor(props: MenuItemProps) { + constructor(props: MenuItemProps & Dependencies) { super(props); autoBind(this); } @@ -431,6 +436,14 @@ export class MenuItem extends React.Component { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion onClick!(evt); + const name = this.elem.querySelectorAll(".title")[0]?.textContent; + + if (name) { + const id = `${window.location.pathname.split("/").pop()} ${name}`; + + this.props.captureClick(id, "Menu Item Click"); + } + if (menu.props.closeOnClickItem && !evt.defaultPrevented) { menu.close(); } @@ -441,7 +454,7 @@ export class MenuItem extends React.Component { } render() { - const { className, disabled, active, spacer, icon, children, ...props } = this.props; + const { className, disabled, active, spacer, icon, children, captureClick, ...props } = this.props; const iconProps: Partial = {}; if (icon) { @@ -474,3 +487,14 @@ export class MenuItem extends React.Component { return
  • ; } } + +export const MenuItem = withInjectables( + NonInjectedMenuItem, + + { + getProps: (di, props) => ({ + captureClick: di.inject(trackWithIdInjectable), + ...props, + }), + }, +); diff --git a/src/renderer/components/select/select.tsx b/src/renderer/components/select/select.tsx index 61b6de1284..b549d72247 100644 --- a/src/renderer/components/select/select.tsx +++ b/src/renderer/components/select/select.tsx @@ -17,6 +17,7 @@ import type { ThemeStore } from "../../themes/store"; import { autoBind, cssNames } from "../../utils"; import { withInjectables } from "@ogre-tools/injectable-react"; import themeStoreInjectable from "../../themes/store.injectable"; +import trackWithIdInjectable from "../../telemetry/track-with-id.injectable"; const { Menu } = components; @@ -81,6 +82,7 @@ const defaultFilter = createFilter({ interface Dependencies { themeStore: ThemeStore; + capture: (id: string, action: string) => void; } export function onMultiSelectFor, Group extends GroupBase