Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 3a241e0

Browse files
Log TimelinePanel debugging info when opening the bug report modal (#8502)
To debug all of the timeline problems: - element-hq/element-web#21613 - element-hq/element-web#21922 - element-hq/element-web#21432 - element-hq/element-web#21533
1 parent d5b363e commit 3a241e0

File tree

4 files changed

+114
-1
lines changed

4 files changed

+114
-1
lines changed

src/components/structures/TimelinePanel.tsx

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,69 @@ class TimelinePanel extends React.Component<IProps, IState> {
371371
}
372372
}
373373

374+
/**
375+
* Logs out debug info to describe the state of the TimelinePanel and the
376+
* events in the room according to the matrix-js-sdk. This is useful when
377+
* debugging problems like messages out of order, or messages that should
378+
* not be showing up in a thread, etc.
379+
*
380+
* It's too expensive and cumbersome to do all of these calculations for
381+
* every message change so instead we only log it out when asked.
382+
*/
383+
private onDumpDebugLogs = (): void => {
384+
const roomId = this.props.timelineSet.room?.roomId;
385+
// Get a list of the event IDs used in this TimelinePanel.
386+
// This includes state and hidden events which we don't render
387+
const eventIdList = this.state.events.map((ev) => ev.getId());
388+
389+
// Get the list of actually rendered events seen in the DOM.
390+
// This is useful to know for sure what's being shown on screen.
391+
// And we can suss out any corrupted React `key` problems.
392+
let renderedEventIds: string[];
393+
const messagePanel = this.messagePanel.current;
394+
if (messagePanel) {
395+
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element;
396+
if (messagePanelNode) {
397+
const actuallyRenderedEvents = messagePanelNode.querySelectorAll('[data-event-id]');
398+
renderedEventIds = [...actuallyRenderedEvents].map((renderedEvent) => {
399+
return renderedEvent.getAttribute('data-event-id');
400+
});
401+
}
402+
}
403+
404+
// Get the list of events and threads for the room as seen by the
405+
// matrix-js-sdk.
406+
let serializedEventIdsFromTimelineSets: { [key: string]: string[] }[];
407+
let serializedEventIdsFromThreadsTimelineSets: { [key: string]: string[] }[];
408+
const serializedThreadsMap: { [key: string]: string[] } = {};
409+
const client = MatrixClientPeg.get();
410+
const room = client?.getRoom(roomId);
411+
if (room) {
412+
const timelineSets = room.getTimelineSets();
413+
const threadsTimelineSets = room.threadsTimelineSets;
414+
415+
// Serialize all of the timelineSets and timelines in each set to their event IDs
416+
serializedEventIdsFromTimelineSets = serializeEventIdsFromTimelineSets(timelineSets);
417+
serializedEventIdsFromThreadsTimelineSets = serializeEventIdsFromTimelineSets(threadsTimelineSets);
418+
419+
// Serialize all threads in the room from theadId -> event IDs in the thread
420+
room.getThreads().forEach((thread) => {
421+
serializedThreadsMap[thread.id] = thread.events.map(ev => ev.getId());
422+
});
423+
}
424+
425+
logger.debug(
426+
`TimelinePanel(${this.context.timelineRenderingType}): Debugging info for ${roomId}\n` +
427+
`\tevents(${eventIdList.length})=${JSON.stringify(eventIdList)}\n` +
428+
`\trenderedEventIds(${renderedEventIds ? renderedEventIds.length : 0})=` +
429+
`${JSON.stringify(renderedEventIds)}\n` +
430+
`\tserializedEventIdsFromTimelineSets=${JSON.stringify(serializedEventIdsFromTimelineSets)}\n` +
431+
`\tserializedEventIdsFromThreadsTimelineSets=` +
432+
`${JSON.stringify(serializedEventIdsFromThreadsTimelineSets)}\n` +
433+
`\tserializedThreadsMap=${JSON.stringify(serializedThreadsMap)}`,
434+
);
435+
};
436+
374437
private onMessageListUnfillRequest = (backwards: boolean, scrollToken: string): void => {
375438
// If backwards, unpaginate from the back (i.e. the start of the timeline)
376439
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
@@ -528,6 +591,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
528591
case "ignore_state_changed":
529592
this.forceUpdate();
530593
break;
594+
case Action.DumpDebugLogs:
595+
this.onDumpDebugLogs();
596+
break;
531597
}
532598
};
533599

@@ -1464,7 +1530,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
14641530
const messagePanel = this.messagePanel.current;
14651531
if (!messagePanel) return null;
14661532

1467-
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as HTMLElement;
1533+
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element;
14681534
if (!messagePanelNode) return null; // sometimes this happens for fresh rooms/post-sync
14691535
const wrapperRect = messagePanelNode.getBoundingClientRect();
14701536
const myUserId = MatrixClientPeg.get().credentials.userId;
@@ -1686,4 +1752,30 @@ class TimelinePanel extends React.Component<IProps, IState> {
16861752
}
16871753
}
16881754

1755+
/**
1756+
* Iterate across all of the timelineSets and timelines inside to expose all of
1757+
* the event IDs contained inside.
1758+
*
1759+
* @return An event ID list for every timeline in every timelineSet
1760+
*/
1761+
function serializeEventIdsFromTimelineSets(timelineSets): { [key: string]: string[] }[] {
1762+
const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => {
1763+
const timelineMap = {};
1764+
1765+
const timelines = timelineSet.getTimelines();
1766+
const liveTimeline = timelineSet.getLiveTimeline();
1767+
1768+
timelines.forEach((timeline, index) => {
1769+
// Add a special label when it is the live timeline so we can tell
1770+
// it apart from the others
1771+
const isLiveTimeline = timeline === liveTimeline;
1772+
timelineMap[isLiveTimeline ? 'liveTimeline' : `${index}`] = timeline.getEvents().map(ev => ev.getId());
1773+
});
1774+
1775+
return timelineMap;
1776+
});
1777+
1778+
return serializedEventIdsInTimelineSet;
1779+
}
1780+
16891781
export default TimelinePanel;

src/components/views/dialogs/BugReportDialog.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import Field from '../elements/Field';
3030
import Spinner from "../elements/Spinner";
3131
import DialogButtons from "../elements/DialogButtons";
3232
import { sendSentryReport } from "../../../sentry";
33+
import defaultDispatcher from '../../../dispatcher/dispatcher';
34+
import { Action } from '../../../dispatcher/actions';
3335

3436
interface IProps {
3537
onFinished: (success: boolean) => void;
@@ -65,6 +67,16 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
6567
downloadProgress: null,
6668
};
6769
this.unmounted = false;
70+
71+
// Get all of the extra info dumped to the console when someone is about
72+
// to send debug logs. Since this is a fire and forget action, we do
73+
// this when the bug report dialog is opened instead of when we submit
74+
// logs because we have no signal to know when all of the various
75+
// components have finished logging. Someone could potentially send logs
76+
// before we fully dump everything but it's probably unlikely.
77+
defaultDispatcher.dispatch({
78+
action: Action.DumpDebugLogs,
79+
});
6880
}
6981

7082
public componentWillUnmount() {

src/components/views/rooms/EventTile.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
12921292
"data-has-reply": !!replyChain,
12931293
"data-layout": this.props.layout,
12941294
"data-self": isOwnEvent,
1295+
"data-event-id": this.props.mxEvent.getId(),
12951296
"onMouseEnter": () => this.setState({ hover: true }),
12961297
"onMouseLeave": () => this.setState({ hover: false }),
12971298
}, [
@@ -1438,6 +1439,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
14381439
"data-scroll-tokens": scrollToken,
14391440
"data-layout": this.props.layout,
14401441
"data-self": isOwnEvent,
1442+
"data-event-id": this.props.mxEvent.getId(),
14411443
"data-has-reply": !!replyChain,
14421444
"onMouseEnter": () => this.setState({ hover: true }),
14431445
"onMouseLeave": () => this.setState({ hover: false }),

src/dispatcher/actions.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,4 +306,11 @@ export enum Action {
306306
* Opens a dialog to add an existing object to a space. Used with a OpenAddExistingToSpaceDialogPayload.
307307
*/
308308
OpenAddToExistingSpaceDialog = "open_add_to_existing_space_dialog",
309+
310+
/**
311+
* Let components know that they should log any useful debugging information
312+
* because we're probably about to send bug report which includes all of the
313+
* logs. Fires with no payload.
314+
*/
315+
DumpDebugLogs = "dump_debug_logs",
309316
}

0 commit comments

Comments
 (0)