Merge pull request #12965 from matrix-org/florianduros/pinned-messages/analytics-event

Add analytics event for pinned messages
This commit is contained in:
David Baker 2024-09-05 16:27:56 +01:00 committed by GitHub
commit 33791cab2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 53 additions and 13 deletions

View file

@ -72,7 +72,7 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
"@matrix-org/analytics-events": "^0.24.0", "@matrix-org/analytics-events": "^0.25.0",
"@matrix-org/emojibase-bindings": "^1.1.2", "@matrix-org/emojibase-bindings": "^1.1.2",
"@matrix-org/matrix-wysiwyg": "2.37.9", "@matrix-org/matrix-wysiwyg": "2.37.9",
"@matrix-org/react-sdk-module-api": "^2.4.0", "@matrix-org/react-sdk-module-api": "^2.4.0",

View file

@ -17,6 +17,7 @@ limitations under the License.
import { PureComponent, SyntheticEvent } from "react"; import { PureComponent, SyntheticEvent } from "react";
import { WebScreen as ScreenEvent } from "@matrix-org/analytics-events/types/typescript/WebScreen"; import { WebScreen as ScreenEvent } from "@matrix-org/analytics-events/types/typescript/WebScreen";
import { Interaction as InteractionEvent } from "@matrix-org/analytics-events/types/typescript/Interaction"; import { Interaction as InteractionEvent } from "@matrix-org/analytics-events/types/typescript/Interaction";
import { PinUnpinAction } from "@matrix-org/analytics-events/types/typescript/PinUnpinAction";
import PageType from "./PageTypes"; import PageType from "./PageTypes";
import Views from "./Views"; import Views from "./Views";
@ -106,6 +107,19 @@ export default class PosthogTrackers {
name, name,
}); });
} }
/**
* Track a pin or unpin action on a message.
* @param kind - Is pin or unpin.
* @param from - From where the action is triggered.
*/
public static trackPinUnpinMessage(kind: PinUnpinAction["kind"], from: PinUnpinAction["from"]): void {
PosthogAnalytics.instance.trackEvent<PinUnpinAction>({
eventName: "PinUnpinAction",
kind,
from,
});
}
} }
export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName }> { export class PosthogScreenTracker extends PureComponent<{ screenName: ScreenName }> {

View file

@ -40,12 +40,13 @@ import { WIDGET_LAYOUT_EVENT_TYPE } from "./stores/widgets/WidgetLayoutStore";
import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases"; import { RightPanelPhases } from "./stores/right-panel/RightPanelStorePhases";
import defaultDispatcher from "./dispatcher/dispatcher"; import defaultDispatcher from "./dispatcher/dispatcher";
import { RoomSettingsTab } from "./components/views/dialogs/RoomSettingsDialog"; import { RoomSettingsTab } from "./components/views/dialogs/RoomSettingsDialog";
import AccessibleButton, { ButtonEvent } from "./components/views/elements/AccessibleButton"; import AccessibleButton from "./components/views/elements/AccessibleButton";
import RightPanelStore from "./stores/right-panel/RightPanelStore"; import RightPanelStore from "./stores/right-panel/RightPanelStore";
import { highlightEvent, isLocationEvent } from "./utils/EventUtils"; import { highlightEvent, isLocationEvent } from "./utils/EventUtils";
import { ElementCall } from "./models/Call"; import { ElementCall } from "./models/Call";
import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoEventType } from "./voice-broadcast"; import { textForVoiceBroadcastStoppedEvent, VoiceBroadcastInfoEventType } from "./voice-broadcast";
import { getSenderName } from "./utils/event/getSenderName"; import { getSenderName } from "./utils/event/getSenderName";
import PosthogTrackers from "./PosthogTrackers.ts";
function getRoomMemberDisplayname(client: MatrixClient, event: MatrixEvent, userId = event.getSender()): string { function getRoomMemberDisplayname(client: MatrixClient, event: MatrixEvent, userId = event.getSender()): string {
const roomId = event.getRoomId(); const roomId = event.getRoomId();
@ -563,6 +564,7 @@ function textForPowerEvent(event: MatrixEvent, client: MatrixClient): (() => str
} }
const onPinnedMessagesClick = (): void => { const onPinnedMessagesClick = (): void => {
PosthogTrackers.trackInteraction("PinnedMessageStateEventClick");
RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false); RightPanelStore.instance.setCard({ phase: RightPanelPhases.PinnedMessages }, false);
}; };
@ -590,7 +592,10 @@ function textForPinnedEvent(event: MatrixEvent, client: MatrixClient, allowJSX:
a: (sub) => ( a: (sub) => (
<AccessibleButton <AccessibleButton
kind="link_inline" kind="link_inline"
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)} onClick={() => {
PosthogTrackers.trackInteraction("PinnedMessageStateEventClick");
highlightEvent(roomId, messageId);
}}
> >
{sub} {sub}
</AccessibleButton> </AccessibleButton>
@ -623,7 +628,10 @@ function textForPinnedEvent(event: MatrixEvent, client: MatrixClient, allowJSX:
a: (sub) => ( a: (sub) => (
<AccessibleButton <AccessibleButton
kind="link_inline" kind="link_inline"
onClick={(e: ButtonEvent) => highlightEvent(roomId, messageId)} onClick={() => {
PosthogTrackers.trackInteraction("PinnedMessageStateEventClick");
highlightEvent(roomId, messageId);
}}
> >
{sub} {sub}
</AccessibleButton> </AccessibleButton>

View file

@ -60,6 +60,7 @@ import { getShareableLocationEvent } from "../../../events/location/getShareable
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
import { CardContext } from "../right_panel/context"; import { CardContext } from "../right_panel/context";
import PinningUtils from "../../../utils/PinningUtils"; import PinningUtils from "../../../utils/PinningUtils";
import PosthogTrackers from "../../../PosthogTrackers.ts";
interface IReplyInThreadButton { interface IReplyInThreadButton {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -243,9 +244,11 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
this.closeMenu(); this.closeMenu();
}; };
private onPinClick = (): void => { private onPinClick = (isPinned: boolean): void => {
// Pin or unpin in background // Pin or unpin in background
PinningUtils.pinOrUnpinEvent(MatrixClientPeg.safeGet(), this.props.mxEvent); PinningUtils.pinOrUnpinEvent(MatrixClientPeg.safeGet(), this.props.mxEvent);
PosthogTrackers.trackPinUnpinMessage(isPinned ? "Pin" : "Unpin", "Timeline");
this.closeMenu(); this.closeMenu();
}; };
@ -618,7 +621,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName={isPinned ? "mx_MessageContextMenu_iconUnpin" : "mx_MessageContextMenu_iconPin"} iconClassName={isPinned ? "mx_MessageContextMenu_iconUnpin" : "mx_MessageContextMenu_iconPin"}
label={isPinned ? _t("action|unpin") : _t("action|pin")} label={isPinned ? _t("action|unpin") : _t("action|pin")}
onClick={this.onPinClick} onClick={() => this.onPinClick(isPinned)}
/> />
); );
} }

View file

@ -22,6 +22,7 @@ import { logger } from "matrix-js-sdk/src/logger";
import BaseDialog from "../dialogs/BaseDialog"; import BaseDialog from "../dialogs/BaseDialog";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import PinningUtils from "../../../utils/PinningUtils.ts"; import PinningUtils from "../../../utils/PinningUtils.ts";
import PosthogTrackers from "../../../PosthogTrackers.ts";
/** /**
* Properties for {@link UnpinAllDialog}. * Properties for {@link UnpinAllDialog}.
@ -61,6 +62,7 @@ export function UnpinAllDialog({ matrixClient, roomId, onFinished }: UnpinAllDia
onClick={async () => { onClick={async () => {
try { try {
await PinningUtils.unpinAllEvents(matrixClient, roomId); await PinningUtils.unpinAllEvents(matrixClient, roomId);
PosthogTrackers.trackPinUnpinMessage("Unpin", "UnpinAll");
} catch (e) { } catch (e) {
logger.error("Failed to unpin all events:", e); logger.error("Failed to unpin all events:", e);
} }

View file

@ -67,6 +67,7 @@ import { GetRelationsForEvent, IEventTileType } from "../rooms/EventTile";
import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast/types"; import { VoiceBroadcastInfoEventType } from "../../../voice-broadcast/types";
import { ButtonEvent } from "../elements/AccessibleButton"; import { ButtonEvent } from "../elements/AccessibleButton";
import PinningUtils from "../../../utils/PinningUtils"; import PinningUtils from "../../../utils/PinningUtils";
import PosthogTrackers from "../../../PosthogTrackers.ts";
interface IOptionsButtonProps { interface IOptionsButtonProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -407,12 +408,13 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
/** /**
* Pin or unpin the event. * Pin or unpin the event.
*/ */
private onPinClick = async (event: ButtonEvent): Promise<void> => { private onPinClick = async (event: ButtonEvent, isPinned: boolean): Promise<void> => {
// Don't open the regular browser or our context menu on right-click // Don't open the regular browser or our context menu on right-click
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
await PinningUtils.pinOrUnpinEvent(MatrixClientPeg.safeGet(), this.props.mxEvent); await PinningUtils.pinOrUnpinEvent(MatrixClientPeg.safeGet(), this.props.mxEvent);
PosthogTrackers.trackPinUnpinMessage(isPinned ? "Pin" : "Unpin", "Timeline");
}; };
public render(): React.ReactNode { public render(): React.ReactNode {
@ -441,8 +443,8 @@ export default class MessageActionBar extends React.PureComponent<IMessageAction
<RovingAccessibleButton <RovingAccessibleButton
className="mx_MessageActionBar_iconButton" className="mx_MessageActionBar_iconButton"
title={isPinned ? _t("action|unpin") : _t("action|pin")} title={isPinned ? _t("action|unpin") : _t("action|pin")}
onClick={this.onPinClick} onClick={(e) => this.onPinClick(e, isPinned)}
onContextMenu={this.onPinClick} onContextMenu={(e: ButtonEvent) => this.onPinClick(e, isPinned)}
key="pin" key="pin"
placement="left" placement="left"
> >

View file

@ -95,6 +95,7 @@ const onRoomFilesClick = (): void => {
}; };
const onRoomPinsClick = (): void => { const onRoomPinsClick = (): void => {
PosthogTrackers.trackInteraction("PinnedMessageRoomInfoButton");
RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true); RightPanelStore.instance.pushCard({ phase: RightPanelPhases.PinnedMessages }, true);
}; };

View file

@ -42,6 +42,7 @@ import { OpenForwardDialogPayload } from "../../../dispatcher/payloads/OpenForwa
import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog"; import { createRedactEventDialog } from "../dialogs/ConfirmRedactDialog";
import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload"; import { ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadPayload";
import PinningUtils from "../../../utils/PinningUtils.ts"; import PinningUtils from "../../../utils/PinningUtils.ts";
import PosthogTrackers from "../../../PosthogTrackers.ts";
const AVATAR_SIZE = "32px"; const AVATAR_SIZE = "32px";
@ -152,6 +153,8 @@ function PinMenu({ event, room, permalinkCreator }: PinMenuProps): JSX.Element {
* View the event in the timeline. * View the event in the timeline.
*/ */
const onViewInTimeline = useCallback(() => { const onViewInTimeline = useCallback(() => {
PosthogTrackers.trackInteraction("PinnedMessageListViewTimeline");
dis.dispatch<ViewRoomPayload>({ dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom, action: Action.ViewRoom,
event_id: event.getId(), event_id: event.getId(),
@ -173,6 +176,7 @@ function PinMenu({ event, room, permalinkCreator }: PinMenuProps): JSX.Element {
*/ */
const onUnpin = useCallback(async (): Promise<void> => { const onUnpin = useCallback(async (): Promise<void> => {
await PinningUtils.pinOrUnpinEvent(matrixClient, event); await PinningUtils.pinOrUnpinEvent(matrixClient, event);
PosthogTrackers.trackPinUnpinMessage("Unpin", "MessagePinningList");
}, [event, matrixClient]); }, [event, matrixClient]);
const contentActionable = isContentActionable(event); const contentActionable = isContentActionable(event);

View file

@ -32,6 +32,7 @@ import dis from "../../../dispatcher/dispatcher";
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import MessageEvent from "../messages/MessageEvent"; import MessageEvent from "../messages/MessageEvent";
import PosthogTrackers from "../../../PosthogTrackers.ts";
/** /**
* The props for the {@link PinnedMessageBanner} component. * The props for the {@link PinnedMessageBanner} component.
@ -68,6 +69,8 @@ export function PinnedMessageBanner({ room, permalinkCreator }: PinnedMessageBan
const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure(); const shouldUseMessageEvent = pinnedEvent.isRedacted() || pinnedEvent.isDecryptionFailure();
const onBannerClick = (): void => { const onBannerClick = (): void => {
PosthogTrackers.trackInteraction("PinnedMessageBannerClick");
// Scroll to the pinned message // Scroll to the pinned message
dis.dispatch<ViewRoomPayload>({ dis.dispatch<ViewRoomPayload>({
action: Action.ViewRoom, action: Action.ViewRoom,
@ -309,6 +312,9 @@ function BannerButton({ room }: BannerButtonProps): JSX.Element {
className="mx_PinnedMessageBanner_actions" className="mx_PinnedMessageBanner_actions"
kind="tertiary" kind="tertiary"
onClick={() => { onClick={() => {
if (isPinnedMessagesPhase) PosthogTrackers.trackInteraction("PinnedMessageBannerCloseListButton");
else PosthogTrackers.trackInteraction("PinnedMessageBannerViewAllButton");
RightPanelStore.instance.showOrHidePhase(RightPanelPhases.PinnedMessages); RightPanelStore.instance.showOrHidePhase(RightPanelPhases.PinnedMessages);
}} }}
> >

View file

@ -1893,10 +1893,10 @@
resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe" resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe"
integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q== integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==
"@matrix-org/analytics-events@^0.24.0": "@matrix-org/analytics-events@^0.25.0":
version "0.24.0" version "0.25.0"
resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.24.0.tgz#21a64537ac975b18e1eb13d9fd0bdc7d448a6039" resolved "https://registry.yarnpkg.com/@matrix-org/analytics-events/-/analytics-events-0.25.0.tgz#b0b85297dc05a67feaf89cc5d70b80283c988141"
integrity sha512-3FDdtqZ+5cMqVffWjFNOIQ7RDFN6XS11kqdtN2ps8uvq5ce8gT0yXQvK37WeKWKZZ5QAKeoMzGhud+lsVcb1xg== integrity sha512-UCTuMjlJGArMqG9qXGfeNz/XtZDFldwuO+dkqP6Wo1nVdWasoWAOlcimDWQ2JnNFCg+UDZU+HLBdS5juTd6xTg==
"@matrix-org/emojibase-bindings@^1.1.2": "@matrix-org/emojibase-bindings@^1.1.2":
version "1.1.3" version "1.1.3"