Basic threads analytics into Posthog MVP (#7871)

This commit is contained in:
Michael Telatynski 2022-02-28 14:11:14 +00:00 committed by GitHub
parent 182aedc3d4
commit 75e41b4c1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 51 additions and 18 deletions

View file

@ -91,7 +91,7 @@
"linkifyjs": "^4.0.0-beta.4", "linkifyjs": "^4.0.0-beta.4",
"lodash": "^4.17.20", "lodash": "^4.17.20",
"maplibre-gl": "^1.15.2", "maplibre-gl": "^1.15.2",
"matrix-analytics-events": "github:matrix-org/matrix-analytics-events.git#8e75aaf0b3e045587daeaf97a7691dbfda2f20c0", "matrix-analytics-events": "github:matrix-org/matrix-analytics-events.git#daad3faed54f0b1f1e026a7498b4653e4d01cd90",
"matrix-events-sdk": "^0.0.1-beta.7", "matrix-events-sdk": "^0.0.1-beta.7",
"matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
"matrix-widget-api": "^0.1.0-beta.18", "matrix-widget-api": "^0.1.0-beta.18",

View file

@ -152,13 +152,13 @@ export class PosthogAnalytics {
// we persist the last `$screen_name` and send it for all events until it is replaced // we persist the last `$screen_name` and send it for all events until it is replaced
private lastScreen: ScreenName = "Loading"; private lastScreen: ScreenName = "Loading";
private sanitizeProperties = (properties: posthog.Properties): posthog.Properties => { private sanitizeProperties = (properties: posthog.Properties, eventName: string): posthog.Properties => {
// Callback from posthog to sanitize properties before sending them to the server. // Callback from posthog to sanitize properties before sending them to the server.
// //
// Here we sanitize posthog's built in properties which leak PII e.g. url reporting. // Here we sanitize posthog's built in properties which leak PII e.g. url reporting.
// See utils.js _.info.properties in posthog-js. // See utils.js _.info.properties in posthog-js.
if (properties["eventName"] === "$pageview") { if (eventName === "$pageview") {
this.lastScreen = properties["$current_url"]; this.lastScreen = properties["$current_url"];
} }
// We inject a screen identifier in $current_url as per https://posthog.com/tutorials/spa // We inject a screen identifier in $current_url as per https://posthog.com/tutorials/spa

View file

@ -38,6 +38,8 @@ import TimelinePanel from './TimelinePanel';
import { Layout } from '../../settings/enums/Layout'; import { Layout } from '../../settings/enums/Layout';
import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../utils/permalinks/Permalinks';
import Measured from '../views/elements/Measured'; import Measured from '../views/elements/Measured';
import PosthogTrackers from "../../PosthogTrackers";
import { ButtonEvent } from "../views/elements/AccessibleButton";
export async function getThreadTimelineSet( export async function getThreadTimelineSet(
client: MatrixClient, client: MatrixClient,
@ -178,7 +180,15 @@ export const ThreadPanelHeader = ({ filterOption, setFilterOption, empty }: {
return <div className="mx_ThreadPanel__header"> return <div className="mx_ThreadPanel__header">
<span>{ _t("Threads") }</span> <span>{ _t("Threads") }</span>
{ !empty && <> { !empty && <>
<ContextMenuButton className="mx_ThreadPanel_dropdown" inputRef={button} isExpanded={menuDisplayed} onClick={() => menuDisplayed ? closeMenu() : openMenu()}> <ContextMenuButton
className="mx_ThreadPanel_dropdown"
inputRef={button}
isExpanded={menuDisplayed}
onClick={(ev: ButtonEvent) => {
openMenu();
PosthogTrackers.trackInteraction("WebRightPanelThreadPanelFilterDropdown", ev);
}}
>
{ `${_t('Show:')} ${value.label}` } { `${_t('Show:')} ${value.label}` }
</ContextMenuButton> </ContextMenuButton>
{ contextMenu } { contextMenu }

View file

@ -50,6 +50,8 @@ import FileDropTarget from "./FileDropTarget";
import { getKeyBindingsManager } from "../../KeyBindingsManager"; import { getKeyBindingsManager } from "../../KeyBindingsManager";
import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts"; import { KeyBindingAction } from "../../accessibility/KeyboardShortcuts";
import Measured from '../views/elements/Measured'; import Measured from '../views/elements/Measured';
import PosthogTrackers from "../../PosthogTrackers";
import { ButtonEvent } from "../views/elements/AccessibleButton";
interface IProps { interface IProps {
room: Room; room: Room;
@ -321,6 +323,9 @@ export default class ThreadView extends React.Component<IProps, IState> {
header={this.renderThreadViewHeader()} header={this.renderThreadViewHeader()}
ref={this.card} ref={this.card}
onKeyDown={this.onKeyDown} onKeyDown={this.onKeyDown}
onBack={(ev: ButtonEvent) => {
PosthogTrackers.trackInteraction("WebThreadViewBackButton", ev);
}}
> >
<Measured <Measured
sensor={this.card.current} sensor={this.card.current}

View file

@ -24,12 +24,13 @@ import classNames from 'classnames';
import Analytics from '../../../Analytics'; import Analytics from '../../../Analytics';
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ButtonEvent } from "../elements/AccessibleButton";
interface IProps { interface IProps {
// Whether this button is highlighted // Whether this button is highlighted
isHighlighted: boolean; isHighlighted: boolean;
// click handler // click handler
onClick: () => void; onClick: (ev: ButtonEvent) => void;
// The parameters to track the click event // The parameters to track the click event
analytics: Parameters<typeof Analytics.trackEvent>; analytics: Parameters<typeof Analytics.trackEvent>;
@ -42,9 +43,9 @@ interface IProps {
// TODO: replace this, the composer buttons and the right panel buttons with a unified representation // TODO: replace this, the composer buttons and the right panel buttons with a unified representation
@replaceableComponent("views.right_panel.HeaderButton") @replaceableComponent("views.right_panel.HeaderButton")
export default class HeaderButton extends React.Component<IProps> { export default class HeaderButton extends React.Component<IProps> {
private onClick = () => { private onClick = (ev: ButtonEvent) => {
Analytics.trackEvent(...this.props.analytics); Analytics.trackEvent(...this.props.analytics);
this.props.onClick(); this.props.onClick(ev);
}; };
public render() { public render() {

View file

@ -38,6 +38,8 @@ import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNo
import { NotificationColor } from "../../../stores/notifications/NotificationColor"; import { NotificationColor } from "../../../stores/notifications/NotificationColor";
import { ThreadsRoomNotificationState } from "../../../stores/notifications/ThreadsRoomNotificationState"; import { ThreadsRoomNotificationState } from "../../../stores/notifications/ThreadsRoomNotificationState";
import { NotificationStateEvents } from "../../../stores/notifications/NotificationState"; import { NotificationStateEvents } from "../../../stores/notifications/NotificationState";
import PosthogTrackers from "../../../PosthogTrackers";
import { ButtonEvent } from "../elements/AccessibleButton";
const ROOM_INFO_PHASES = [ const ROOM_INFO_PHASES = [
RightPanelPhases.RoomSummary, RightPanelPhases.RoomSummary,
@ -207,11 +209,12 @@ export default class RoomHeaderButtons extends HeaderButtons<IProps> {
this.setPhase(RightPanelPhases.Timeline); this.setPhase(RightPanelPhases.Timeline);
}; };
private onThreadsPanelClicked = () => { private onThreadsPanelClicked = (ev: ButtonEvent) => {
if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) { if (RoomHeaderButtons.THREAD_PHASES.includes(this.state.phase)) {
RightPanelStore.instance.togglePanel(); RightPanelStore.instance.togglePanel();
} else { } else {
showThreadPanel(); showThreadPanel();
PosthogTrackers.trackInteraction("WebRoomHeaderButtonsThreadsButton", ev);
} }
}; };

View file

@ -15,7 +15,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import React, { createRef } from 'react'; import React, { createRef, MouseEvent } from 'react';
import classNames from "classnames"; import classNames from "classnames";
import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event"; import { EventType, MsgType, RelationType } from "matrix-js-sdk/src/@types/event";
import { EventStatus, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event"; import { EventStatus, MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
@ -81,6 +81,7 @@ import { DecryptionFailureTracker } from '../../../DecryptionFailureTracker';
import RedactedBody from '../messages/RedactedBody'; import RedactedBody from '../messages/RedactedBody';
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { shouldDisplayReply } from '../../../utils/Reply'; import { shouldDisplayReply } from '../../../utils/Reply';
import PosthogTrackers from "../../../PosthogTrackers";
export type GetRelationsForEvent = (eventId: string, relationType: string, eventType: string) => Relations; export type GetRelationsForEvent = (eventId: string, relationType: string, eventType: string) => Relations;
@ -684,17 +685,19 @@ export default class EventTile extends React.Component<IProps, IState> {
return ( return (
<CardContext.Consumer> <CardContext.Consumer>
{ context => { context =>
<div <AccessibleButton
className="mx_ThreadInfo" className="mx_ThreadInfo"
onClick={() => { onClick={(ev: ButtonEvent) => {
showThread({ rootEvent: this.props.mxEvent, push: context.isCard }); showThread({ rootEvent: this.props.mxEvent, push: context.isCard });
PosthogTrackers.trackInteraction("WebRoomTimelineThreadSummaryButton", ev);
}} }}
aria-label={_t("Open thread")}
> >
<span className="mx_ThreadInfo_threads-amount"> <span className="mx_ThreadInfo_threads-amount">
{ count } { count }
</span> </span>
{ this.renderThreadLastMessagePreview() } { this.renderThreadLastMessagePreview() }
</div> </AccessibleButton>
} }
</CardContext.Consumer> </CardContext.Consumer>
); );
@ -1507,7 +1510,12 @@ export default class EventTile extends React.Component<IProps, IState> {
"data-notification": this.state.threadNotification, "data-notification": this.state.threadNotification,
"onMouseEnter": () => this.setState({ hover: true }), "onMouseEnter": () => this.setState({ hover: true }),
"onMouseLeave": () => this.setState({ hover: false }), "onMouseLeave": () => this.setState({ hover: false }),
"onClick": () => showThread({ rootEvent: this.props.mxEvent, push: true }), "onClick": (ev: MouseEvent) => {
showThread({ rootEvent: this.props.mxEvent, push: true });
const target = ev.currentTarget as HTMLElement;
const index = Array.from(target.parentElement.children).indexOf(target);
PosthogTrackers.trackInteraction("WebThreadsPanelThreadItem", ev, index);
},
}, <> }, <>
{ sender } { sender }
{ avatar } { avatar }

View file

@ -345,12 +345,17 @@ export class SendMessageComposer extends React.Component<ISendMessageComposerPro
return; return;
} }
PosthogAnalytics.instance.trackEvent<ComposerEvent>({ const posthogEvent: ComposerEvent = {
eventName: "Composer", eventName: "Composer",
isEditing: false, isEditing: false,
inThread: this.props.relation?.rel_type === RelationType.Thread,
isReply: !!this.props.replyToEvent, isReply: !!this.props.replyToEvent,
}); inThread: this.props.relation?.rel_type === RelationType.Thread,
};
if (posthogEvent.inThread) {
const threadRoot = this.props.room.findEventById(this.props.relation.event_id);
posthogEvent.startsThread = threadRoot?.getThread()?.events.length === 1;
}
PosthogAnalytics.instance.trackEvent<ComposerEvent>(posthogEvent);
// Replace emoticon at the end of the message // Replace emoticon at the end of the message
if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) { if (SettingsStore.getValue('MessageComposerInput.autoReplaceEmoji')) {

View file

@ -1659,6 +1659,7 @@
"From a thread": "From a thread", "From a thread": "From a thread",
"%(count)s reply|other": "%(count)s replies", "%(count)s reply|other": "%(count)s replies",
"%(count)s reply|one": "%(count)s reply", "%(count)s reply|one": "%(count)s reply",
"Open thread": "Open thread",
"This event could not be displayed": "This event could not be displayed", "This event could not be displayed": "This event could not be displayed",
"Your key share request has been sent - please check your other sessions for key share requests.": "Your key share request has been sent - please check your other sessions for key share requests.", "Your key share request has been sent - please check your other sessions for key share requests.": "Your key share request has been sent - please check your other sessions for key share requests.",
"Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.", "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.": "Key share requests are sent to your other sessions automatically. If you rejected or dismissed the key share request on your other sessions, click here to request the keys for this session again.",

View file

@ -6261,9 +6261,9 @@ mathml-tag-names@^2.1.3:
resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==
"matrix-analytics-events@github:matrix-org/matrix-analytics-events.git#8e75aaf0b3e045587daeaf97a7691dbfda2f20c0": "matrix-analytics-events@github:matrix-org/matrix-analytics-events.git#daad3faed54f0b1f1e026a7498b4653e4d01cd90":
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/matrix-org/matrix-analytics-events/tar.gz/8e75aaf0b3e045587daeaf97a7691dbfda2f20c0" resolved "https://codeload.github.com/matrix-org/matrix-analytics-events/tar.gz/daad3faed54f0b1f1e026a7498b4653e4d01cd90"
matrix-events-sdk@^0.0.1-beta.6: matrix-events-sdk@^0.0.1-beta.6:
version "0.0.1-beta.6" version "0.0.1-beta.6"