Error resistant data dump from TimelinePanel (#9079)

Error resistant data dump from `TimelinePanel` so we still have some data to dump if one of the pieces errors out.

Example error that can happen (as seen in https://github.com/matrix-org/element-web-rageshakes/issues/14197):

```
Uncaught TypeError: Cannot read properties of null (reading 'getEvents')
    at TimelineWindow.getEvents (timeline-window.ts:378:37)
    at TimelinePanel_TimelinePanel.onDumpDebugLogs (TimelinePanel.tsx:434:60)
    at Object.ID_727 (TimelinePanel.tsx:609:22)
    at MatrixDispatcher._invokeCallback (Dispatcher.js:198:1)
    at MatrixDispatcher.dispatch (Dispatcher.js:174:1)
    at sentryWrapped (helpers.js:77:1)
```
This commit is contained in:
Eric Eastwood 2022-07-22 11:38:44 -05:00 committed by GitHub
parent e694e87814
commit dfa844a035
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -384,24 +384,28 @@ class TimelinePanel extends React.Component<IProps, IState> {
* every message change so instead we only log it out when asked.
*/
private onDumpDebugLogs = (): void => {
const room = this.props.timelineSet.room;
const room = this.props.timelineSet?.room;
// Get a list of the event IDs used in this TimelinePanel.
// This includes state and hidden events which we don't render
const eventIdList = this.state.events.map((ev) => ev.getId());
const eventIdList = this.state?.events?.map((ev) => ev.getId());
// Get the list of actually rendered events seen in the DOM.
// This is useful to know for sure what's being shown on screen.
// And we can suss out any corrupted React `key` problems.
let renderedEventIds: string[];
const messagePanel = this.messagePanel.current;
if (messagePanel) {
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element;
if (messagePanelNode) {
const actuallyRenderedEvents = messagePanelNode.querySelectorAll('[data-event-id]');
renderedEventIds = [...actuallyRenderedEvents].map((renderedEvent) => {
return renderedEvent.getAttribute('data-event-id');
});
try {
const messagePanel = this.messagePanel.current;
if (messagePanel) {
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element;
if (messagePanelNode) {
const actuallyRenderedEvents = messagePanelNode.querySelectorAll('[data-event-id]');
renderedEventIds = [...actuallyRenderedEvents].map((renderedEvent) => {
return renderedEvent.getAttribute('data-event-id');
});
}
}
} catch (err) {
logger.error(`onDumpDebugLogs: Failed to get the actual event ID's in the DOM`, err);
}
// Get the list of events and threads for the room as seen by the
@ -413,26 +417,44 @@ class TimelinePanel extends React.Component<IProps, IState> {
const timelineSets = room.getTimelineSets();
const threadsTimelineSets = room.threadsTimelineSets;
// Serialize all of the timelineSets and timelines in each set to their event IDs
serializedEventIdsFromTimelineSets = serializeEventIdsFromTimelineSets(timelineSets);
serializedEventIdsFromThreadsTimelineSets = serializeEventIdsFromTimelineSets(threadsTimelineSets);
try {
// Serialize all of the timelineSets and timelines in each set to their event IDs
serializedEventIdsFromTimelineSets = serializeEventIdsFromTimelineSets(timelineSets);
serializedEventIdsFromThreadsTimelineSets = serializeEventIdsFromTimelineSets(threadsTimelineSets);
} catch (err) {
logger.error(`onDumpDebugLogs: Failed to serialize event IDs from timelinesets`, err);
}
// Serialize all threads in the room from theadId -> event IDs in the thread
room.getThreads().forEach((thread) => {
serializedThreadsMap[thread.id] = {
events: thread.events.map(ev => ev.getId()),
numTimelines: thread.timelineSet.getTimelines().length,
liveTimeline: thread.timelineSet.getLiveTimeline().getEvents().length,
prevTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Backward)
?.getEvents().length,
nextTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Forward)
?.getEvents().length,
};
});
try {
// Serialize all threads in the room from theadId -> event IDs in the thread
room.getThreads().forEach((thread) => {
serializedThreadsMap[thread.id] = {
events: thread.events.map(ev => ev.getId()),
numTimelines: thread.timelineSet.getTimelines().length,
liveTimeline: thread.timelineSet.getLiveTimeline().getEvents().length,
prevTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Backward)
?.getEvents().length,
nextTimeline: thread.timelineSet.getLiveTimeline().getNeighbouringTimeline(Direction.Forward)
?.getEvents().length,
};
});
} catch (err) {
logger.error(`onDumpDebugLogs: Failed to serialize event IDs from the threads`, err);
}
}
const timelineWindowEventIds = this.timelineWindow.getEvents().map(ev => ev.getId());
const pendingEvents = this.props.timelineSet.getPendingEvents().map(ev => ev.getId());
let timelineWindowEventIds: string[];
try {
timelineWindowEventIds = this.timelineWindow.getEvents().map(ev => ev.getId());
} catch (err) {
logger.error(`onDumpDebugLogs: Failed to get event IDs from the timelineWindow`, err);
}
let pendingEventIds: string[];
try {
pendingEventIds = this.props.timelineSet.getPendingEvents().map(ev => ev.getId());
} catch (err) {
logger.error(`onDumpDebugLogs: Failed to get pending event IDs`, err);
}
logger.debug(
`TimelinePanel(${this.context.timelineRenderingType}): Debugging info for ${room?.roomId}\n` +
@ -444,7 +466,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
`${JSON.stringify(serializedEventIdsFromThreadsTimelineSets)}\n` +
`\tserializedThreadsMap=${JSON.stringify(serializedThreadsMap)}\n` +
`\ttimelineWindowEventIds(${timelineWindowEventIds.length})=${JSON.stringify(timelineWindowEventIds)}\n` +
`\tpendingEvents(${pendingEvents.length})=${JSON.stringify(pendingEvents)}`,
`\tpendingEventIds(${pendingEventIds.length})=${JSON.stringify(pendingEventIds)}`,
);
};