Wire up bunch of interaction events into Posthog (#7707)

This commit is contained in:
Michael Telatynski 2022-02-09 14:42:08 +00:00 committed by GitHub
parent 5620b83d34
commit 999e1b7421
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 120 additions and 26 deletions

View file

@ -14,8 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { PureComponent } from "react";
import { PureComponent, SyntheticEvent } from "react";
import { Screen as ScreenEvent } from "matrix-analytics-events/types/typescript/Screen";
import { Interaction as InteractionEvent } from "matrix-analytics-events/types/typescript/Interaction";
import PageType from "./PageTypes";
import Views from "./Views";
@ -88,6 +89,21 @@ export default class PosthogTrackers {
this.override = null;
this.trackPage();
}
public static trackInteraction(name: InteractionEvent["name"], ev?: SyntheticEvent): void {
let interactionType: InteractionEvent["interactionType"];
if (ev?.type === "click") {
interactionType = "Pointer";
} else if (ev?.type.startsWith("key")) {
interactionType = "Keyboard";
}
PosthogAnalytics.instance.trackEvent<InteractionEvent>({
eventName: "Interaction",
interactionType,
name,
});
}
}
export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName }> {

View file

@ -25,6 +25,7 @@ import * as ContentHelpers from 'matrix-js-sdk/src/content-helpers';
import { parseFragment as parseHtml, Element as ChildElement } from "parse5";
import { logger } from "matrix-js-sdk/src/logger";
import { IContent } from 'matrix-js-sdk/src/models/event';
import { SlashCommand as SlashCommandEvent } from "matrix-analytics-events/types/typescript/SlashCommand";
import { MatrixClientPeg } from './MatrixClientPeg';
import dis from './dispatcher/dispatcher';
@ -62,6 +63,7 @@ import { shouldShowComponent } from "./customisations/helpers/UIComponents";
import { TimelineRenderingType } from './contexts/RoomContext';
import RoomViewStore from "./stores/RoomViewStore";
import { XOR } from "./@types/common";
import { PosthogAnalytics } from "./PosthogAnalytics";
// XXX: workaround for https://github.com/microsoft/TypeScript/issues/31816
interface HTMLInputEvent extends Event {
@ -105,6 +107,7 @@ interface ICommandOpts {
aliases?: string[];
args?: string;
description: string;
analyticsName?: SlashCommandEvent["command"];
runFn?: RunFn;
category: string;
hideCompletionAfterSpace?: boolean;
@ -121,6 +124,7 @@ export class Command {
public readonly category: string;
public readonly hideCompletionAfterSpace: boolean;
public readonly renderingTypes?: TimelineRenderingType[];
public readonly analyticsName?: SlashCommandEvent["command"];
private readonly _isEnabled?: () => boolean;
constructor(opts: ICommandOpts) {
@ -133,6 +137,7 @@ export class Command {
this.hideCompletionAfterSpace = opts.hideCompletionAfterSpace || false;
this._isEnabled = opts.isEnabled;
this.renderingTypes = opts.renderingTypes;
this.analyticsName = opts.analyticsName;
}
public getCommand() {
@ -167,6 +172,13 @@ export class Command {
);
}
if (this.analyticsName) {
PosthogAnalytics.instance.trackEvent<SlashCommandEvent>({
eventName: "SlashCommand",
command: this.analyticsName,
});
}
return this.runFn.bind(this)(roomId, args);
}
@ -488,6 +500,7 @@ export const Commands = [
command: 'invite',
args: '<user-id> [<reason>]',
description: _td('Invites user with given id to current room'),
analyticsName: "invite",
isEnabled: () => shouldShowComponent(UIComponent.InviteUsers),
runFn: function(roomId, args) {
if (args) {
@ -674,6 +687,7 @@ export const Commands = [
command: 'part',
args: '[<room-address>]',
description: _td('Leave room'),
analyticsName: "part",
runFn: function(roomId, args) {
const cli = MatrixClientPeg.get();

View file

@ -36,7 +36,6 @@ import { EchoChamber } from "../../../stores/local-echo/EchoChamber";
import { RoomNotifState } from "../../../RoomNotifs";
import Modal from "../../../Modal";
import ExportDialog from "../dialogs/ExportDialog";
import { onRoomFilesClick, onRoomMembersClick } from "../right_panel/RoomSummaryCard";
import RoomViewStore from "../../../stores/RoomViewStore";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import { ROOM_NOTIFICATIONS_TAB } from "../dialogs/RoomSettingsDialog";
@ -44,6 +43,7 @@ import { useEventEmitterState } from "../../../hooks/useEventEmitter";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import DMRoomMap from "../../../utils/DMRoomMap";
import { Action } from "../../../dispatcher/actions";
import PosthogTrackers from "../../../PosthogTrackers";
interface IProps extends IContextMenuProps {
room: Room;
@ -86,6 +86,8 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
room_id: room.roomId,
});
onFinished();
PosthogTrackers.trackInteraction("WebRoomHeaderContextMenuLeaveItem", ev);
};
leaveOption = <IconizedContextMenuOption
@ -109,6 +111,8 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
roomId: room.roomId,
});
onFinished();
PosthogTrackers.trackInteraction("WebRoomHeaderContextMenuInviteItem", ev);
};
inviteOption = <IconizedContextMenuOption
@ -124,7 +128,10 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
if (room.getMyMembership() === "join") {
const isFavorite = roomTags.includes(DefaultTagID.Favourite);
favouriteOption = <IconizedContextMenuCheckbox
onClick={(e) => onTagRoom(e, DefaultTagID.Favourite)}
onClick={(e) => {
onTagRoom(e, DefaultTagID.Favourite);
PosthogTrackers.trackInteraction("WebRoomHeaderContextMenuFavouriteToggle", e);
}}
active={isFavorite}
label={isFavorite ? _t("Favourited") : _t("Favourite")}
iconClassName="mx_RoomTile_iconStar"
@ -171,6 +178,8 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
initial_tab_id: ROOM_NOTIFICATIONS_TAB,
});
onFinished();
PosthogTrackers.trackInteraction("WebRoomHeaderContextMenuNotificationsItem", ev);
}}
label={_t("Notifications")}
iconClassName={iconClassName}
@ -190,8 +199,9 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
ev.stopPropagation();
ensureViewingRoom();
onRoomMembersClick(false);
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, false);
onFinished();
PosthogTrackers.trackInteraction("WebRoomHeaderContextMenuPeopleItem", ev);
}}
label={_t("People")}
iconClassName="mx_RoomTile_iconPeople"
@ -258,7 +268,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
ev.stopPropagation();
ensureViewingRoom();
onRoomFilesClick(false);
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, false);
onFinished();
}}
label={_t("Files")}
@ -291,6 +301,7 @@ const RoomContextMenu = ({ room, onFinished, ...props }: IProps) => {
room_id: room.roomId,
});
onFinished();
PosthogTrackers.trackInteraction("WebRoomHeaderContextMenuSettingsItem", ev);
}}
label={_t("Settings")}
iconClassName="mx_RoomTile_iconSettings"

View file

@ -19,7 +19,7 @@ import classNames from 'classnames';
import AutoHideScrollbar from "../../structures/AutoHideScrollbar";
import { _t } from "../../../languageHandler";
import AccessibleButton from "../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
import { backLabelForPhase } from '../../../stores/right-panel/RightPanelStorePhases';
@ -30,8 +30,9 @@ interface IProps {
className?: string;
withoutScrollContainer?: boolean;
closeLabel?: string;
onClose?(): void;
cardState?;
onClose?(ev: ButtonEvent): void;
onBack?(ev: ButtonEvent): void;
cardState?: any;
}
interface IGroupProps {
@ -49,6 +50,7 @@ export const Group: React.FC<IGroupProps> = ({ className, title, children }) =>
const BaseCard: React.FC<IProps> = ({
closeLabel,
onClose,
onBack,
className,
header,
footer,
@ -59,7 +61,8 @@ const BaseCard: React.FC<IProps> = ({
const cardHistory = RightPanelStore.instance.roomPhaseHistory;
if (cardHistory.length > 1) {
const prevCard = cardHistory[cardHistory.length - 2];
const onBackClick = () => {
const onBackClick = (ev: ButtonEvent) => {
onBack?.(ev);
RightPanelStore.instance.popCard();
};
const label = backLabelForPhase(prevCard.phase) ?? _t("Back");

View file

@ -23,7 +23,7 @@ import { useIsEncrypted } from '../../../hooks/useIsEncrypted';
import BaseCard, { Group } from "./BaseCard";
import { _t } from '../../../languageHandler';
import RoomAvatar from "../avatars/RoomAvatar";
import AccessibleButton from "../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePhases';
import Modal from "../../../Modal";
@ -47,6 +47,7 @@ import RoomName from "../elements/RoomName";
import UIStore from "../../../stores/UIStore";
import ExportDialog from "../dialogs/ExportDialog";
import RightPanelStore from "../../../stores/right-panel/RightPanelStore";
import PosthogTrackers from "../../../PosthogTrackers";
interface IProps {
room: Room;
@ -59,7 +60,7 @@ interface IAppsSectionProps {
interface IButtonProps {
className: string;
onClick(): void;
onClick(ev: ButtonEvent): void;
}
const Button: React.FC<IButtonProps> = ({ children, className, onClick }) => {
@ -229,16 +230,18 @@ const AppsSection: React.FC<IAppsSectionProps> = ({ room }) => {
</Group>;
};
export const onRoomMembersClick = (allowClose = true) => {
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, allowClose);
const onRoomMembersClick = (ev: ButtonEvent) => {
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.RoomMemberList }, true);
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoPeopleButton", ev);
};
export const onRoomFilesClick = (allowClose = true) => {
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, allowClose);
const onRoomFilesClick = () => {
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.FilePanel }, true);
};
const onRoomSettingsClick = () => {
const onRoomSettingsClick = (ev: ButtonEvent) => {
defaultDispatcher.dispatch({ action: "open_room_settings" });
PosthogTrackers.trackInteraction("WebRightPanelRoomInfoSettingsButton", ev);
};
const RoomSummaryCard: React.FC<IProps> = ({ room, onClose }) => {

View file

@ -34,7 +34,7 @@ import Modal from '../../../Modal';
import { _t } from '../../../languageHandler';
import createRoom, { findDMForUser, privateShouldBeEncrypted } from '../../../createRoom';
import DMRoomMap from '../../../utils/DMRoomMap';
import AccessibleButton from '../elements/AccessibleButton';
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
import SdkConfig from '../../../SdkConfig';
import RoomViewStore from "../../../stores/RoomViewStore";
import MultiInviter from "../../../utils/MultiInviter";
@ -78,6 +78,7 @@ import RightPanelStore from '../../../stores/right-panel/RightPanelStore';
import { IRightPanelCardState } from '../../../stores/right-panel/RightPanelStoreIPanelState';
import { useUserStatusMessage } from "../../../hooks/useUserStatusMessage";
import UserIdentifierCustomisations from '../../../customisations/UserIdentifier';
import PosthogTrackers from "../../../PosthogTrackers";
export interface IDevice {
deviceId: string;
@ -422,7 +423,7 @@ const UserOptionsSection: React.FC<{
if (canInvite && (member?.membership ?? 'leave') === 'leave' && shouldShowComponent(UIComponent.InviteUsers)) {
const roomId = member && member.roomId ? member.roomId : RoomViewStore.getRoomId();
const onInviteUserButton = async () => {
const onInviteUserButton = async (ev: ButtonEvent) => {
try {
// We use a MultiInviter to re-use the invite logic, even though
// we're only inviting one user.
@ -438,6 +439,8 @@ const UserOptionsSection: React.FC<{
description: ((err && err.message) ? err.message : _t("Operation failed")),
});
}
PosthogTrackers.trackInteraction("WebRightPanelRoomUserInfoInviteButton", ev);
};
inviteUserButton = (
@ -1719,6 +1722,11 @@ const UserInfo: React.FC<IProps> = ({
onClose={onClose}
closeLabel={closeLabel}
cardState={cardState}
onBack={(ev: ButtonEvent) => {
if (RightPanelStore.instance.previousCard.phase === RightPanelPhases.RoomMemberList) {
PosthogTrackers.trackInteraction("WebRightPanelRoomUserInfoBackButton", ev);
}
}}
>
{ content }
</BaseCard>;

View file

@ -20,6 +20,7 @@ import { EventStatus, IContent, MatrixEvent } from 'matrix-js-sdk/src/models/eve
import { MsgType } from 'matrix-js-sdk/src/@types/event';
import { Room } from 'matrix-js-sdk/src/models/room';
import { logger } from "matrix-js-sdk/src/logger";
import { Composer as ComposerEvent } from "matrix-analytics-events/types/typescript/Composer";
import { _t } from '../../../languageHandler';
import dis from '../../../dispatcher/dispatcher';
@ -46,6 +47,7 @@ import RoomContext from '../../../contexts/RoomContext';
import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { PosthogAnalytics } from "../../../PosthogAnalytics";
function getHtmlReplyFallback(mxEvent: MatrixEvent): string {
const html = mxEvent.getContent().formatted_body;
@ -295,9 +297,17 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
private sendEdit = async (): Promise<void> => {
if (this.state.saveDisabled) return;
const startTime = CountlyAnalytics.getTimestamp();
const editedEvent = this.props.editState.getEvent();
PosthogAnalytics.instance.trackEvent<ComposerEvent>({
eventName: "Composer",
isEditing: true,
inThread: !!editedEvent?.getThread(),
isReply: !!editedEvent.replyEventId,
});
const startTime = CountlyAnalytics.getTimestamp();
// Replace emoticon at the end of the message
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
const caret = this.editorRef.current?.getCaret();
@ -323,7 +333,7 @@ class EditMessageComposer extends React.Component<IEditMessageComposerProps, ISt
if (!containsEmote(this.model) && isSlashCommand(this.model)) {
const [cmd, args, commandText] = getSlashCommand(this.model);
if (cmd) {
const threadId = this.props.editState?.getEvent()?.getThread()?.id || null;
const threadId = editedEvent?.getThread()?.id || null;
if (cmd.category === CommandCategories.messages) {
editContent["m.new_content"] = await runSlashCommand(cmd, args, roomId, threadId);
if (!editContent["m.new_content"]) {

View file

@ -40,13 +40,14 @@ import SettingsStore from "../../../settings/SettingsStore";
import TruncatedList from '../elements/TruncatedList';
import Spinner from "../elements/Spinner";
import SearchBox from "../../structures/SearchBox";
import AccessibleButton from '../elements/AccessibleButton';
import AccessibleButton, { ButtonEvent } from '../elements/AccessibleButton';
import EntityTile from "./EntityTile";
import MemberTile from "./MemberTile";
import BaseAvatar from '../avatars/BaseAvatar';
import SpaceStore from "../../../stores/spaces/SpaceStore";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
import { UIComponent } from "../../../settings/UIFeature";
import PosthogTrackers from "../../../PosthogTrackers";
const INITIAL_LOAD_NUM_MEMBERS = 30;
const INITIAL_LOAD_NUM_INVITED = 5;
@ -595,7 +596,9 @@ export default class MemberList extends React.Component<IProps, IState> {
</BaseCard>;
}
onInviteButtonClick = (): void => {
private onInviteButtonClick = (ev: ButtonEvent): void => {
PosthogTrackers.trackInteraction("WebRightPanelMemberListInviteButton", ev);
if (MatrixClientPeg.get().isGuest()) {
dis.dispatch({ action: 'require_registration' });
return;

View file

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { createRef } from 'react';
import classNames from 'classnames';
import { MatrixEvent, IEventRelation } from "matrix-js-sdk/src/models/event";
@ -47,12 +48,13 @@ import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
import RoomContext from '../../../contexts/RoomContext';
import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
import MessageComposerButtons from './MessageComposerButtons';
import { ButtonEvent } from '../elements/AccessibleButton';
let instanceCount = 0;
const NARROW_MODE_BREAKPOINT = 500;
interface ISendButtonProps {
onClick: () => void;
onClick: (ev: ButtonEvent) => void;
title?: string; // defaults to something generic
}

View file

@ -52,6 +52,7 @@ import IconizedContextMenu, {
} from "../context_menus/IconizedContextMenu";
import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/CommunityPrototypeStore";
import { replaceableComponent } from "../../../utils/replaceableComponent";
import PosthogTrackers from "../../../PosthogTrackers";
interface IProps {
room: Room;
@ -255,6 +256,8 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
ev.stopPropagation();
const target = ev.target as HTMLButtonElement;
this.setState({ notificationsMenuPosition: target.getBoundingClientRect() });
PosthogTrackers.trackInteraction("WebRoomListRoomTileNotificationsMenu", ev);
};
private onCloseNotificationsMenu = () => {
@ -322,6 +325,8 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
room_id: this.props.room.roomId,
});
this.setState({ generalMenuPosition: null }); // hide the menu
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuLeaveItem", ev);
};
private onForgetRoomClick = (ev: ButtonEvent) => {
@ -344,6 +349,8 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
room_id: this.props.room.roomId,
});
this.setState({ generalMenuPosition: null }); // hide the menu
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuSettingsItem", ev);
};
private onCopyRoomClick = (ev: ButtonEvent) => {
@ -366,6 +373,8 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
roomId: this.props.room.roomId,
});
this.setState({ generalMenuPosition: null }); // hide the menu
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuInviteItem", ev);
};
private async saveNotifState(ev: ButtonEvent, newState: RoomNotifState) {
@ -500,7 +509,10 @@ export default class RoomTile extends React.PureComponent<IProps, IState> {
>
<IconizedContextMenuOptionList>
<IconizedContextMenuCheckbox
onClick={(e) => this.onTagRoom(e, DefaultTagID.Favourite)}
onClick={(e) => {
this.onTagRoom(e, DefaultTagID.Favourite);
PosthogTrackers.trackInteraction("WebRoomListRoomTileContextMenuFavouriteToggle", e);
}}
active={isFavorite}
label={favouriteLabel}
iconClassName="mx_RoomTile_iconStar"

View file

@ -21,6 +21,7 @@ import { DebouncedFunc, throttle } from 'lodash';
import { EventType, RelationType } from "matrix-js-sdk/src/@types/event";
import { logger } from "matrix-js-sdk/src/logger";
import { Room } from 'matrix-js-sdk/src/models/room';
import { Composer as ComposerEvent } from "matrix-analytics-events/types/typescript/Composer";
import dis from '../../../dispatcher/dispatcher';
import EditorModel from '../../../editor/model';
@ -57,6 +58,7 @@ import DocumentPosition from "../../../editor/position";
import { ComposerType } from "../../../dispatcher/payloads/ComposerInsertPayload";
import { getSlashCommand, isSlashCommand, runSlashCommand, shouldSendAnyway } from "../../../editor/commands";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import { PosthogAnalytics } from "../../../PosthogAnalytics";
interface IAddReplyOpts {
permalinkCreator?: RoomPermalinkCreator;
@ -344,6 +346,13 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
return;
}
PosthogAnalytics.instance.trackEvent<ComposerEvent>({
eventName: "Composer",
isEditing: false,
inThread: this.props.relation?.rel_type === RelationType.Thread,
isReply: !!this.props.replyToEvent,
});
// Replace emoticon at the end of the message
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {
const indexOfLastPart = model.parts.length - 1;

View file

@ -18,7 +18,7 @@ import React, { ContextType } from 'react';
import { _t } from "../../../../../languageHandler";
import RoomProfileSettings from "../../../room_settings/RoomProfileSettings";
import AccessibleButton from "../../../elements/AccessibleButton";
import AccessibleButton, { ButtonEvent } from "../../../elements/AccessibleButton";
import dis from "../../../../../dispatcher/dispatcher";
import MatrixClientContext from "../../../../../contexts/MatrixClientContext";
import SettingsStore from "../../../../../settings/SettingsStore";
@ -27,6 +27,7 @@ import { replaceableComponent } from "../../../../../utils/replaceableComponent"
import UrlPreviewSettings from "../../../room_settings/UrlPreviewSettings";
import RelatedGroupSettings from "../../../room_settings/RelatedGroupSettings";
import AliasSettings from "../../../room_settings/AliasSettings";
import PosthogTrackers from "../../../../../PosthogTrackers";
interface IProps {
roomId: string;
@ -49,11 +50,13 @@ export default class GeneralRoomSettingsTab extends React.Component<IProps, ISta
};
}
private onLeaveClick = (): void => {
private onLeaveClick = (ev: ButtonEvent): void => {
dis.dispatch({
action: 'leave_room',
room_id: this.props.roomId,
});
PosthogTrackers.trackInteraction("WebRoomSettingsLeaveButton", ev);
};
public render(): JSX.Element {