mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Auto capture
Signed-off-by: Juho Heikka <juho.heikka@gmail.com>
This commit is contained in:
parent
f162c8b6eb
commit
98b88180d8
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "kube-object-event-status",
|
"name": "kube-object-event-status",
|
||||||
"version": "0.0.1",
|
"version": "5.5.0-alpha.0.1651664036833",
|
||||||
"description": "Adds kube object status from events",
|
"description": "Adds kube object status from events",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-metrics-cluster-feature",
|
"name": "lens-metrics-cluster-feature",
|
||||||
"version": "0.0.1",
|
"version": "5.5.0-alpha.0.1651664036833",
|
||||||
"description": "Lens metrics cluster feature",
|
"description": "Lens metrics cluster feature",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-node-menu",
|
"name": "lens-node-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.5.0-alpha.0.1651664036833",
|
||||||
"description": "Lens node menu",
|
"description": "Lens node menu",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lens-pod-menu",
|
"name": "lens-pod-menu",
|
||||||
"version": "0.0.1",
|
"version": "5.5.0-alpha.0.1651664036833",
|
||||||
"description": "Lens pod menu",
|
"description": "Lens pod menu",
|
||||||
"renderer": "dist/renderer.js",
|
"renderer": "dist/renderer.js",
|
||||||
"lens": {
|
"lens": {
|
||||||
|
|||||||
@ -8,6 +8,11 @@ import type { ButtonHTMLAttributes } from "react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
import { withTooltip } from "../tooltip";
|
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<any> {
|
export interface ButtonProps extends ButtonHTMLAttributes<any> {
|
||||||
label?: React.ReactNode;
|
label?: React.ReactNode;
|
||||||
@ -25,10 +30,10 @@ export interface ButtonProps extends ButtonHTMLAttributes<any> {
|
|||||||
target?: "_blank"; // in case of using @href
|
target?: "_blank"; // in case of using @href
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Button = withTooltip((props: ButtonProps) => {
|
const NonInjectedButton = withTooltip((props: ButtonProps & Dependencies) => {
|
||||||
const {
|
const {
|
||||||
waiting, label, primary, accent, plain, hidden, active, big,
|
waiting, label, primary, accent, plain, hidden, active, big,
|
||||||
round, outlined, light, children, ...btnProps
|
round, outlined, light, children, track, ...btnProps
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
if (hidden) return null;
|
if (hidden) return null;
|
||||||
@ -37,10 +42,18 @@ export const Button = withTooltip((props: ButtonProps) => {
|
|||||||
waiting, primary, accent, plain, active, big, round, outlined, light,
|
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
|
// render as link
|
||||||
if (props.href) {
|
if (props.href) {
|
||||||
return (
|
return (
|
||||||
<a {...btnProps}>
|
<a {...btnProps} onClick={onClick}>
|
||||||
{label}
|
{label}
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
@ -49,9 +62,24 @@ export const Button = withTooltip((props: ButtonProps) => {
|
|||||||
|
|
||||||
// render as button
|
// render as button
|
||||||
return (
|
return (
|
||||||
<button type="button" {...btnProps}>
|
<button
|
||||||
|
type="button"
|
||||||
|
{...btnProps}
|
||||||
|
onClick={onClick}>
|
||||||
{label}
|
{label}
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const Button = withInjectables<Dependencies, ButtonProps>(
|
||||||
|
NonInjectedButton,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
track: di.inject(trackEventInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -7,6 +7,8 @@ import "./checkbox.scss";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import type { SingleOrMany } from "../../utils";
|
import type { SingleOrMany } from "../../utils";
|
||||||
import { cssNames, noop } 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 {
|
export interface CheckboxProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -18,7 +20,11 @@ export interface CheckboxProps {
|
|||||||
children?: SingleOrMany<React.ReactChild | React.ReactFragment>;
|
children?: SingleOrMany<React.ReactChild | React.ReactFragment>;
|
||||||
}
|
}
|
||||||
|
|
||||||
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, {
|
const componentClass = cssNames("Checkbox flex align-center", className, {
|
||||||
inline,
|
inline,
|
||||||
checked: value,
|
checked: value,
|
||||||
@ -32,7 +38,13 @@ export function Checkbox({ label, inline, className, value, children, onChange =
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={value}
|
checked={value}
|
||||||
disabled={disabled}
|
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);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<i className="box flex align-center"/>
|
<i className="box flex align-center"/>
|
||||||
{label ? <span className="label">{label}</span> : null}
|
{label ? <span className="label">{label}</span> : null}
|
||||||
@ -40,3 +52,14 @@ export function Checkbox({ label, inline, className, value, children, onChange =
|
|||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Checkbox = withInjectables<Dependencies, CheckboxProps>(
|
||||||
|
NonInjectedCheckbox,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
captureClick: di.inject(trackWithIdInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import { Tooltip } from "../tooltip";
|
|||||||
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable";
|
||||||
|
import trackWithIdInjectable from "../../../renderer/telemetry/track-with-id.injectable";
|
||||||
|
|
||||||
export interface HotbarIconProps extends AvatarProps {
|
export interface HotbarIconProps extends AvatarProps {
|
||||||
uid: string;
|
uid: string;
|
||||||
@ -32,6 +33,7 @@ export interface HotbarIconProps extends AvatarProps {
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
normalizeMenuItem: NormalizeCatalogEntityContextMenu;
|
||||||
|
capture: (id: string, action: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonInjectedHotbarIcon = observer(({
|
const NonInjectedHotbarIcon = observer(({
|
||||||
@ -41,7 +43,7 @@ const NonInjectedHotbarIcon = observer(({
|
|||||||
normalizeMenuItem,
|
normalizeMenuItem,
|
||||||
...props
|
...props
|
||||||
}: HotbarIconProps & Dependencies) => {
|
}: 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 id = `hotbarIcon-${uid}`;
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
const [menuOpen, setMenuOpen] = useState(false);
|
||||||
|
|
||||||
@ -65,8 +67,13 @@ const NonInjectedHotbarIcon = observer(({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
size={size}
|
size={size}
|
||||||
src={src}
|
src={src}
|
||||||
onClick={(event) => !disabled && onClick?.(event)}
|
onClick={(event) => {
|
||||||
>
|
if (disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
capture(title, "Click Hotbar Item");
|
||||||
|
onClick?.(event);
|
||||||
|
}}>
|
||||||
{material && <Icon material={material} />}
|
{material && <Icon material={material} />}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
{children}
|
{children}
|
||||||
@ -100,5 +107,6 @@ export const HotbarIcon = withInjectables<Dependencies, HotbarIconProps>(NonInje
|
|||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
...props,
|
...props,
|
||||||
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable),
|
||||||
|
capture: di.inject(trackWithIdInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import userStoreInjectable from "../../../common/user-store/user-store.injectabl
|
|||||||
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
|
import pageFiltersStoreInjectable from "./page-filters/store.injectable";
|
||||||
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable";
|
||||||
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
import openConfirmDialogInjectable from "../confirm-dialog/open.injectable";
|
||||||
|
import trackWithIdInjectable from "../../../renderer/telemetry/track-with-id.injectable";
|
||||||
|
|
||||||
export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStores extends boolean> {
|
export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStores extends boolean> {
|
||||||
getFilters: () => Filter[];
|
getFilters: () => Filter[];
|
||||||
@ -75,6 +76,7 @@ interface Dependencies {
|
|||||||
userStore: UserStore;
|
userStore: UserStore;
|
||||||
pageFiltersStore: PageFiltersStore;
|
pageFiltersStore: PageFiltersStore;
|
||||||
openConfirmDialog: OpenConfirmDialog;
|
openConfirmDialog: OpenConfirmDialog;
|
||||||
|
capture: (id: string, action: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -110,7 +112,11 @@ class NonInjectedItemListLayoutContent<
|
|||||||
searchItem={item}
|
searchItem={item}
|
||||||
sortItem={item}
|
sortItem={item}
|
||||||
selected={detailsItem && detailsItem.getId() === item.getId()}
|
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)}
|
{...customizeTableRowProps(item)}
|
||||||
>
|
>
|
||||||
{isSelectable && (
|
{isSelectable && (
|
||||||
@ -381,5 +387,6 @@ export const ItemListLayoutContent = withInjectables<Dependencies, ItemListLayou
|
|||||||
userStore: di.inject(userStoreInjectable),
|
userStore: di.inject(userStoreInjectable),
|
||||||
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
|
pageFiltersStore: di.inject(pageFiltersStoreInjectable),
|
||||||
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
openConfirmDialog: di.inject(openConfirmDialogInjectable),
|
||||||
|
capture: di.inject(trackWithIdInjectable),
|
||||||
}),
|
}),
|
||||||
}) as <Item extends ItemObject, PreLoadStores extends boolean>(props: ItemListLayoutContentProps<Item, PreLoadStores>) => React.ReactElement;
|
}) as <Item extends ItemObject, PreLoadStores extends boolean>(props: ItemListLayoutContentProps<Item, PreLoadStores>) => React.ReactElement;
|
||||||
|
|||||||
@ -8,13 +8,26 @@ import styles from "./close-button.module.scss";
|
|||||||
import type { HTMLAttributes } from "react";
|
import type { HTMLAttributes } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import trackWithIdInjectable from "../../telemetry/track-with-id.injectable";
|
||||||
|
|
||||||
export interface CloseButtonProps extends HTMLAttributes<HTMLDivElement> {
|
export interface CloseButtonProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CloseButton(props: CloseButtonProps) {
|
interface Dependencies {
|
||||||
|
capture: (id: string, action: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function NonInjectedCloseButton(props: CloseButtonProps & Dependencies) {
|
||||||
|
const { capture, ...rest } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div {...props}>
|
<div
|
||||||
|
{...rest}
|
||||||
|
onClick={(e) => {
|
||||||
|
capture(`${window.location.pathname}`, "Close Button Click");
|
||||||
|
props?.onClick(e);
|
||||||
|
}}>
|
||||||
<div
|
<div
|
||||||
className={styles.closeButton}
|
className={styles.closeButton}
|
||||||
role="button"
|
role="button"
|
||||||
@ -28,3 +41,14 @@ export function CloseButton(props: CloseButtonProps) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CloseButton = withInjectables<Dependencies, CloseButtonProps>(
|
||||||
|
NonInjectedCloseButton,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
capture: di.inject(trackWithIdInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -16,9 +16,11 @@ import { withInjectables } from "@ogre-tools/injectable-react";
|
|||||||
import type { SidebarStorageState } from "./sidebar-storage/sidebar-storage.injectable";
|
import type { SidebarStorageState } from "./sidebar-storage/sidebar-storage.injectable";
|
||||||
import sidebarStorageInjectable from "./sidebar-storage/sidebar-storage.injectable";
|
import sidebarStorageInjectable from "./sidebar-storage/sidebar-storage.injectable";
|
||||||
import type { HierarchicalSidebarItem } from "./sidebar-items.injectable";
|
import type { HierarchicalSidebarItem } from "./sidebar-items.injectable";
|
||||||
|
import trackWithIdInjectable from "../../../renderer/telemetry/track-with-id.injectable";
|
||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
sidebarStorage: StorageLayer<SidebarStorageState>;
|
sidebarStorage: StorageLayer<SidebarStorageState>;
|
||||||
|
capture: (id: string, action: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SidebarItemProps {
|
export interface SidebarItemProps {
|
||||||
@ -96,6 +98,8 @@ class NonInjectedSidebarItem extends React.Component<
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
|
this.props.capture(this.registration.title.toString(), "Click Side Bar Item");
|
||||||
|
|
||||||
if (this.isExpandable) {
|
if (this.isExpandable) {
|
||||||
this.toggleExpand();
|
this.toggleExpand();
|
||||||
} else {
|
} else {
|
||||||
@ -125,5 +129,7 @@ export const SidebarItem = withInjectables<Dependencies, SidebarItemProps>(NonIn
|
|||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
...props,
|
...props,
|
||||||
sidebarStorage: di.inject(sidebarStorageInjectable),
|
sidebarStorage: di.inject(sidebarStorageInjectable),
|
||||||
|
capture: di.inject(trackWithIdInjectable),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -11,17 +11,23 @@ import { cssNames } from "../../utils";
|
|||||||
import { Tab, Tabs } from "../tabs";
|
import { Tab, Tabs } from "../tabs";
|
||||||
import { ErrorBoundary } from "../error-boundary";
|
import { ErrorBoundary } from "../error-boundary";
|
||||||
import type { HierarchicalSidebarItem } from "./sidebar-items.injectable";
|
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 {
|
export interface TabLayoutProps {
|
||||||
tabs?: HierarchicalSidebarItem[];
|
tabs?: HierarchicalSidebarItem[];
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TabLayout = observer(
|
const NonInjectedTabLayout = observer(
|
||||||
({
|
({
|
||||||
tabs = [],
|
tabs = [],
|
||||||
children,
|
children,
|
||||||
}: TabLayoutProps) => {
|
captureClick,
|
||||||
|
}: TabLayoutProps & Dependencies) => {
|
||||||
const hasTabs = tabs.length > 0;
|
const hasTabs = tabs.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -37,7 +43,10 @@ export const TabLayout = observer(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tab
|
<Tab
|
||||||
onClick={registration.onClick}
|
onClick={() => {
|
||||||
|
captureClick(registration.title.toString(), "Tab Click");
|
||||||
|
registration.onClick();
|
||||||
|
}}
|
||||||
key={registration.id}
|
key={registration.id}
|
||||||
label={registration.title}
|
label={registration.title}
|
||||||
active={active}
|
active={active}
|
||||||
@ -58,3 +67,14 @@ export const TabLayout = observer(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const TabLayout = withInjectables<Dependencies, TabLayoutProps>(
|
||||||
|
NonInjectedTabLayout,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
captureClick: di.inject(trackWithIdInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import { Animate } from "../animate";
|
|||||||
import type { IconProps } from "../icon";
|
import type { IconProps } from "../icon";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
import isEqual from "lodash/isEqual";
|
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<MenuContextValue | null>(null);
|
export const MenuContext = React.createContext<MenuContextValue | null>(null);
|
||||||
export type MenuContextValue = Menu;
|
export type MenuContextValue = Menu;
|
||||||
@ -71,7 +73,7 @@ export class Menu extends React.Component<MenuProps, State> {
|
|||||||
}
|
}
|
||||||
public opener: HTMLElement | null = null;
|
public opener: HTMLElement | null = null;
|
||||||
public elem: HTMLUListElement | null = null;
|
public elem: HTMLUListElement | null = null;
|
||||||
protected items: { [index: number]: MenuItem } = {};
|
protected items: { [index: number]: NonInjectedMenuItem } = {};
|
||||||
public state: State = {};
|
public state: State = {};
|
||||||
|
|
||||||
get isOpen() {
|
get isOpen() {
|
||||||
@ -308,7 +310,7 @@ export class Menu extends React.Component<MenuProps, State> {
|
|||||||
this.elem = elem;
|
this.elem = elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bindItemRef(item: MenuItem, index: number) {
|
protected bindItemRef(item: NonInjectedMenuItem, index: number) {
|
||||||
this.items[index] = item;
|
this.items[index] = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,7 +330,7 @@ export class Menu extends React.Component<MenuProps, State> {
|
|||||||
const menuItems = React.Children.toArray(children).map((item, index) => {
|
const menuItems = React.Children.toArray(children).map((item, index) => {
|
||||||
if (typeof item === "object" && (item as ReactElement).type === MenuItem) {
|
if (typeof item === "object" && (item as ReactElement).type === MenuItem) {
|
||||||
return React.cloneElement(item as ReactElement, {
|
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<MenuProps>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
captureClick: (id: string, action: string) => void;
|
||||||
|
}
|
||||||
export interface MenuItemProps extends React.HTMLProps<any> {
|
export interface MenuItemProps extends React.HTMLProps<any> {
|
||||||
icon?: string | Partial<IconProps>;
|
icon?: string | Partial<IconProps>;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
@ -401,14 +406,14 @@ const defaultPropsMenuItem: Partial<MenuItemProps> = {
|
|||||||
onClick: noop,
|
onClick: noop,
|
||||||
};
|
};
|
||||||
|
|
||||||
export class MenuItem extends React.Component<MenuItemProps> {
|
class NonInjectedMenuItem extends React.Component<MenuItemProps & Dependencies> {
|
||||||
static defaultProps = defaultPropsMenuItem as object;
|
static defaultProps = defaultPropsMenuItem as object;
|
||||||
static contextType = MenuContext;
|
static contextType = MenuContext;
|
||||||
|
|
||||||
declare context: MenuContextValue;
|
declare context: MenuContextValue;
|
||||||
public elem: HTMLElement | null = null;
|
public elem: HTMLElement | null = null;
|
||||||
|
|
||||||
constructor(props: MenuItemProps) {
|
constructor(props: MenuItemProps & Dependencies) {
|
||||||
super(props);
|
super(props);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
@ -431,6 +436,14 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
onClick!(evt);
|
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) {
|
if (menu.props.closeOnClickItem && !evt.defaultPrevented) {
|
||||||
menu.close();
|
menu.close();
|
||||||
}
|
}
|
||||||
@ -441,7 +454,7 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
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<IconProps> = {};
|
const iconProps: Partial<IconProps> = {};
|
||||||
|
|
||||||
if (icon) {
|
if (icon) {
|
||||||
@ -474,3 +487,14 @@ export class MenuItem extends React.Component<MenuItemProps> {
|
|||||||
return <li {...elemProps}/>;
|
return <li {...elemProps}/>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MenuItem = withInjectables<Dependencies, MenuItemProps>(
|
||||||
|
NonInjectedMenuItem,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
captureClick: di.inject(trackWithIdInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import type { ThemeStore } from "../../themes/store";
|
|||||||
import { autoBind, cssNames } from "../../utils";
|
import { autoBind, cssNames } from "../../utils";
|
||||||
import { withInjectables } from "@ogre-tools/injectable-react";
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
import themeStoreInjectable from "../../themes/store.injectable";
|
import themeStoreInjectable from "../../themes/store.injectable";
|
||||||
|
import trackWithIdInjectable from "../../telemetry/track-with-id.injectable";
|
||||||
|
|
||||||
const { Menu } = components;
|
const { Menu } = components;
|
||||||
|
|
||||||
@ -81,6 +82,7 @@ const defaultFilter = createFilter({
|
|||||||
|
|
||||||
interface Dependencies {
|
interface Dependencies {
|
||||||
themeStore: ThemeStore;
|
themeStore: ThemeStore;
|
||||||
|
capture: (id: string, action: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function onMultiSelectFor<Value, Option extends SelectOption<Value>, Group extends GroupBase<Option> = GroupBase<Option>>(collection: Set<Value> | ObservableSet<Value>): SelectProps<Value, Option, true, Group>["onChange"] {
|
export function onMultiSelectFor<Value, Option extends SelectOption<Value>, Group extends GroupBase<Option> = GroupBase<Option>>(collection: Set<Value> | ObservableSet<Value>): SelectProps<Value, Option, true, Group>["onChange"] {
|
||||||
@ -228,7 +230,13 @@ class NonInjectedSelect<
|
|||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
className={cssNames("Select", this.themeClass, className)}
|
className={cssNames("Select", this.themeClass, className)}
|
||||||
classNamePrefix="Select"
|
classNamePrefix="Select"
|
||||||
onChange={action(onChange)} // This is done so that all changes are actionable
|
onChange={action(() => {
|
||||||
|
if (inputId) {
|
||||||
|
props.capture(inputId, "Select Change");
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange();
|
||||||
|
})} // This is done so that all changes are actionable
|
||||||
components={{
|
components={{
|
||||||
...components,
|
...components,
|
||||||
Menu: ({ className, ...props }) => (
|
Menu: ({ className, ...props }) => (
|
||||||
@ -249,6 +257,7 @@ export const Select = withInjectables<Dependencies, SelectProps<unknown, SelectO
|
|||||||
getProps: (di, props) => ({
|
getProps: (di, props) => ({
|
||||||
...props,
|
...props,
|
||||||
themeStore: di.inject(themeStoreInjectable),
|
themeStore: di.inject(themeStoreInjectable),
|
||||||
|
capture: di.inject(trackWithIdInjectable),
|
||||||
}),
|
}),
|
||||||
}) as <
|
}) as <
|
||||||
Value,
|
Value,
|
||||||
|
|||||||
@ -8,12 +8,18 @@ import styles from "./switch.module.scss";
|
|||||||
import type { ChangeEvent, HTMLProps } from "react";
|
import type { ChangeEvent, HTMLProps } from "react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { cssNames } from "../../utils";
|
import { cssNames } from "../../utils";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import trackWithIdInjectable from "../../telemetry/track-with-id.injectable";
|
||||||
|
|
||||||
export interface SwitchProps extends Omit<HTMLProps<HTMLInputElement>, "onChange"> {
|
export interface SwitchProps extends Omit<HTMLProps<HTMLInputElement>, "onChange"> {
|
||||||
onChange?: (checked: boolean, event: ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (checked: boolean, event: ChangeEvent<HTMLInputElement>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Switch({ children, disabled, onChange, ...props }: SwitchProps) {
|
interface Dependencies {
|
||||||
|
captureChange: (id: string, action: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function NonInjectedSwitch({ children, disabled, onChange, captureChange, ...props }: SwitchProps & Dependencies) {
|
||||||
return (
|
return (
|
||||||
<label className={cssNames(styles.Switch, { [styles.disabled]: disabled })} data-testid="switch">
|
<label className={cssNames(styles.Switch, { [styles.disabled]: disabled })} data-testid="switch">
|
||||||
{children}
|
{children}
|
||||||
@ -21,9 +27,23 @@ export function Switch({ children, disabled, onChange, ...props }: SwitchProps)
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
role="switch"
|
role="switch"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={(event) => onChange?.(event.target.checked, event)}
|
onChange={(event) =>{
|
||||||
|
onChange?.(event.target.checked, event);
|
||||||
|
captureChange(children.toString(), `Switch ${props.checked ? "On" : "Off"}`);
|
||||||
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Switch = withInjectables<Dependencies, SwitchProps>(
|
||||||
|
NonInjectedSwitch,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
captureChange: di.inject(trackWithIdInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import type { DOMAttributes } from "react";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { autoBind, cssNames } from "../../utils";
|
import { autoBind, cssNames } from "../../utils";
|
||||||
import { Icon } from "../icon";
|
import { Icon } from "../icon";
|
||||||
|
import { withInjectables } from "@ogre-tools/injectable-react";
|
||||||
|
import trackWithIdInjectable from "../../../renderer/telemetry/track-with-id.injectable";
|
||||||
|
|
||||||
const TabsContext = React.createContext<TabsContextValue>({});
|
const TabsContext = React.createContext<TabsContextValue>({});
|
||||||
|
|
||||||
@ -59,12 +61,12 @@ export interface TabProps<D = any> extends DOMAttributes<HTMLElement> {
|
|||||||
value?: D;
|
value?: D;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Tab extends React.PureComponent<TabProps> {
|
class NonInjectedTab extends React.PureComponent<TabProps & Dependencies> {
|
||||||
static contextType = TabsContext;
|
static contextType = TabsContext;
|
||||||
declare context: TabsContextValue;
|
declare context: TabsContextValue;
|
||||||
public ref = React.createRef<HTMLDivElement>();
|
public ref = React.createRef<HTMLDivElement>();
|
||||||
|
|
||||||
constructor(props: TabProps) {
|
constructor(props: TabProps & Dependencies) {
|
||||||
super(props);
|
super(props);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
}
|
}
|
||||||
@ -91,6 +93,9 @@ export class Tab extends React.PureComponent<TabProps> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value?.kind) {
|
||||||
|
this.props.capture(`${value?.kind} `, "Tab Click");
|
||||||
|
}
|
||||||
onClick?.(evt);
|
onClick?.(evt);
|
||||||
this.context.onChange?.(value);
|
this.context.onChange?.(value);
|
||||||
}
|
}
|
||||||
@ -142,3 +147,18 @@ export class Tab extends React.PureComponent<TabProps> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Dependencies {
|
||||||
|
capture: (id: string, action: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tab = withInjectables<Dependencies, TabProps>(
|
||||||
|
NonInjectedTab,
|
||||||
|
|
||||||
|
{
|
||||||
|
getProps: (di, props) => ({
|
||||||
|
capture: di.inject(trackWithIdInjectable),
|
||||||
|
...props,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
85
src/renderer/telemetry/telemetry.injectable.ts
Normal file
85
src/renderer/telemetry/telemetry.injectable.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { AppEvent } from "../../common/app-event-bus/event-bus";
|
||||||
|
import appEventBusInjectable from "../../common/app-event-bus/app-event-bus.injectable";
|
||||||
|
import type { EventEmitter } from "../../common/event-emitter";
|
||||||
|
|
||||||
|
function getButtonEventName(el: HTMLElement) {
|
||||||
|
let headers: string[] = [];
|
||||||
|
const levels = 3;
|
||||||
|
let parent = el;
|
||||||
|
|
||||||
|
for (let i = 0; i < levels; i++) {
|
||||||
|
if (parent.parentElement) {
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodelist = parent.querySelectorAll("h1, h2, h3, h4, h5, h6, .header");
|
||||||
|
|
||||||
|
nodelist.forEach(node => headers.push(node.textContent));
|
||||||
|
|
||||||
|
if (headers.length === 0) {
|
||||||
|
const path = window.location.pathname.split("/");
|
||||||
|
|
||||||
|
headers.push(path[path.length-1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = [...new Set(headers)];
|
||||||
|
headers.push(el.textContent);
|
||||||
|
const buttonEventName = headers.join(" ");
|
||||||
|
|
||||||
|
return buttonEventName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// foo_bar-baz => Foo Bar Baz
|
||||||
|
function getNameFromId(id: string) {
|
||||||
|
return id.split(/[_,-]/).map((part) => `${part[0].toUpperCase()+part.substring(1)}`).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Telemetry {
|
||||||
|
private eventBus: EventEmitter<[AppEvent]>;
|
||||||
|
private destination = "jep";
|
||||||
|
private debug = false;
|
||||||
|
|
||||||
|
constructor(eventBus: EventEmitter<[AppEvent]>) {
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitEvent(action: string, name: string) {
|
||||||
|
console.log(`[TELEMETRY]: ${action} ${name}`);
|
||||||
|
|
||||||
|
if (!this.debug) {
|
||||||
|
this.eventBus.emit({
|
||||||
|
destination: this.destination,
|
||||||
|
name,
|
||||||
|
action,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonClickEvent(e: React.MouseEvent) {
|
||||||
|
const el = e.target as HTMLElement;
|
||||||
|
|
||||||
|
this.emitEvent("Click", getButtonEventName(el));
|
||||||
|
}
|
||||||
|
|
||||||
|
selectChangeEvent(id: string) {
|
||||||
|
this.emitEvent("Select Change", getNameFromId(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
tableRowClick(id: string) {
|
||||||
|
this.emitEvent("Table Row Click", getNameFromId(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const telemetryInjectable = getInjectable({
|
||||||
|
id: "telemetry",
|
||||||
|
instantiate: (di) => new Telemetry(di.inject(appEventBusInjectable)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default telemetryInjectable;
|
||||||
63
src/renderer/telemetry/track-event.injectable.ts
Normal file
63
src/renderer/telemetry/track-event.injectable.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { AppEvent } from "../../common/app-event-bus/event-bus";
|
||||||
|
import appEventBusInjectable from "../../common/app-event-bus/app-event-bus.injectable";
|
||||||
|
import type { EventEmitter } from "../../common/event-emitter";
|
||||||
|
import capitalize from "lodash/capitalize";
|
||||||
|
|
||||||
|
function getEventName(el: HTMLElement) {
|
||||||
|
let headers: string[] = [];
|
||||||
|
const levels = 3;
|
||||||
|
let parent = el;
|
||||||
|
|
||||||
|
const path = window.location.pathname.split("/");
|
||||||
|
|
||||||
|
headers.push(capitalize(path[path.length-1]));
|
||||||
|
|
||||||
|
for (let i = 0; i < levels; i++) {
|
||||||
|
if (parent.parentElement) {
|
||||||
|
parent = parent.parentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodelist = parent.querySelectorAll("h1, h2, h3, h4, h5, h6, .header");
|
||||||
|
|
||||||
|
nodelist.forEach(node => node.textContent && headers.push(node.textContent));
|
||||||
|
|
||||||
|
headers = [...new Set(headers)];
|
||||||
|
|
||||||
|
if (el?.textContent) {
|
||||||
|
headers.push(el.textContent);
|
||||||
|
}
|
||||||
|
const eventName = headers.join(" ");
|
||||||
|
|
||||||
|
return eventName;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackEventName(eventBus: EventEmitter<[AppEvent]>, event: React.MouseEvent) {
|
||||||
|
const name = getEventName(event.target as HTMLElement);
|
||||||
|
const action = capitalize(event.type);
|
||||||
|
|
||||||
|
console.log("track event name");
|
||||||
|
console.log(`${action} ${name}`);
|
||||||
|
|
||||||
|
eventBus.emit({
|
||||||
|
destination: "MixPanel",
|
||||||
|
name,
|
||||||
|
action: capitalize(event.type),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const trackEventInjectable = getInjectable({
|
||||||
|
id: "track-mouse-event",
|
||||||
|
instantiate: (di) => {
|
||||||
|
return (event: React.MouseEvent) => {
|
||||||
|
return trackEventName(di.inject(appEventBusInjectable), event);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default trackEventInjectable;
|
||||||
36
src/renderer/telemetry/track-with-id.injectable.ts
Normal file
36
src/renderer/telemetry/track-with-id.injectable.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||||
|
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||||
|
*/
|
||||||
|
import { getInjectable } from "@ogre-tools/injectable";
|
||||||
|
import type { AppEvent } from "../../common/app-event-bus/event-bus";
|
||||||
|
import appEventBusInjectable from "../../common/app-event-bus/app-event-bus.injectable";
|
||||||
|
import type { EventEmitter } from "../../common/event-emitter";
|
||||||
|
|
||||||
|
// foo_bar-baz => Foo Bar Baz
|
||||||
|
function getNameFromId(id: string) {
|
||||||
|
return id.split(/[/,-,--]/).filter(Boolean).map((part) => `${part[0].toUpperCase()+part.substring(1)}`).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function trackWithId(eventBus: EventEmitter<[AppEvent]>, id: string, action: string) {
|
||||||
|
const target = getNameFromId(id);
|
||||||
|
|
||||||
|
console.log(`[trackWithId]: ${name}`);
|
||||||
|
|
||||||
|
eventBus.emit({
|
||||||
|
name: target,
|
||||||
|
action,
|
||||||
|
destination: "MixPanel",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const trackWithIdInjectable = getInjectable({
|
||||||
|
id: "track-with-id",
|
||||||
|
instantiate: (di) => {
|
||||||
|
return (id: string, action: string) => {
|
||||||
|
return trackWithId(di.inject(appEventBusInjectable), id, action);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default trackWithIdInjectable;
|
||||||
Loading…
Reference in New Issue
Block a user