Log TimelinePanel
debugging info when opening the bug report modal (#8502)
To debug all of the timeline problems: - https://github.com/vector-im/element-web/issues/21613 - https://github.com/vector-im/element-web/issues/21922 - https://github.com/vector-im/element-web/issues/21432 - https://github.com/vector-im/element-web/issues/21533
This commit is contained in:
parent
d5b363e971
commit
3a241e0dfb
4 changed files with 114 additions and 1 deletions
|
@ -371,6 +371,69 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs out debug info to describe the state of the TimelinePanel and the
|
||||
* events in the room according to the matrix-js-sdk. This is useful when
|
||||
* debugging problems like messages out of order, or messages that should
|
||||
* not be showing up in a thread, etc.
|
||||
*
|
||||
* It's too expensive and cumbersome to do all of these calculations for
|
||||
* every message change so instead we only log it out when asked.
|
||||
*/
|
||||
private onDumpDebugLogs = (): void => {
|
||||
const roomId = this.props.timelineSet.room?.roomId;
|
||||
// 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());
|
||||
|
||||
// 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');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get the list of events and threads for the room as seen by the
|
||||
// matrix-js-sdk.
|
||||
let serializedEventIdsFromTimelineSets: { [key: string]: string[] }[];
|
||||
let serializedEventIdsFromThreadsTimelineSets: { [key: string]: string[] }[];
|
||||
const serializedThreadsMap: { [key: string]: string[] } = {};
|
||||
const client = MatrixClientPeg.get();
|
||||
const room = client?.getRoom(roomId);
|
||||
if (room) {
|
||||
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);
|
||||
|
||||
// Serialize all threads in the room from theadId -> event IDs in the thread
|
||||
room.getThreads().forEach((thread) => {
|
||||
serializedThreadsMap[thread.id] = thread.events.map(ev => ev.getId());
|
||||
});
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`TimelinePanel(${this.context.timelineRenderingType}): Debugging info for ${roomId}\n` +
|
||||
`\tevents(${eventIdList.length})=${JSON.stringify(eventIdList)}\n` +
|
||||
`\trenderedEventIds(${renderedEventIds ? renderedEventIds.length : 0})=` +
|
||||
`${JSON.stringify(renderedEventIds)}\n` +
|
||||
`\tserializedEventIdsFromTimelineSets=${JSON.stringify(serializedEventIdsFromTimelineSets)}\n` +
|
||||
`\tserializedEventIdsFromThreadsTimelineSets=` +
|
||||
`${JSON.stringify(serializedEventIdsFromThreadsTimelineSets)}\n` +
|
||||
`\tserializedThreadsMap=${JSON.stringify(serializedThreadsMap)}`,
|
||||
);
|
||||
};
|
||||
|
||||
private onMessageListUnfillRequest = (backwards: boolean, scrollToken: string): void => {
|
||||
// If backwards, unpaginate from the back (i.e. the start of the timeline)
|
||||
const dir = backwards ? EventTimeline.BACKWARDS : EventTimeline.FORWARDS;
|
||||
|
@ -528,6 +591,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
case "ignore_state_changed":
|
||||
this.forceUpdate();
|
||||
break;
|
||||
case Action.DumpDebugLogs:
|
||||
this.onDumpDebugLogs();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1464,7 +1530,7 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
const messagePanel = this.messagePanel.current;
|
||||
if (!messagePanel) return null;
|
||||
|
||||
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as HTMLElement;
|
||||
const messagePanelNode = ReactDOM.findDOMNode(messagePanel) as Element;
|
||||
if (!messagePanelNode) return null; // sometimes this happens for fresh rooms/post-sync
|
||||
const wrapperRect = messagePanelNode.getBoundingClientRect();
|
||||
const myUserId = MatrixClientPeg.get().credentials.userId;
|
||||
|
@ -1686,4 +1752,30 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate across all of the timelineSets and timelines inside to expose all of
|
||||
* the event IDs contained inside.
|
||||
*
|
||||
* @return An event ID list for every timeline in every timelineSet
|
||||
*/
|
||||
function serializeEventIdsFromTimelineSets(timelineSets): { [key: string]: string[] }[] {
|
||||
const serializedEventIdsInTimelineSet = timelineSets.map((timelineSet) => {
|
||||
const timelineMap = {};
|
||||
|
||||
const timelines = timelineSet.getTimelines();
|
||||
const liveTimeline = timelineSet.getLiveTimeline();
|
||||
|
||||
timelines.forEach((timeline, index) => {
|
||||
// Add a special label when it is the live timeline so we can tell
|
||||
// it apart from the others
|
||||
const isLiveTimeline = timeline === liveTimeline;
|
||||
timelineMap[isLiveTimeline ? 'liveTimeline' : `${index}`] = timeline.getEvents().map(ev => ev.getId());
|
||||
});
|
||||
|
||||
return timelineMap;
|
||||
});
|
||||
|
||||
return serializedEventIdsInTimelineSet;
|
||||
}
|
||||
|
||||
export default TimelinePanel;
|
||||
|
|
|
@ -30,6 +30,8 @@ import Field from '../elements/Field';
|
|||
import Spinner from "../elements/Spinner";
|
||||
import DialogButtons from "../elements/DialogButtons";
|
||||
import { sendSentryReport } from "../../../sentry";
|
||||
import defaultDispatcher from '../../../dispatcher/dispatcher';
|
||||
import { Action } from '../../../dispatcher/actions';
|
||||
|
||||
interface IProps {
|
||||
onFinished: (success: boolean) => void;
|
||||
|
@ -65,6 +67,16 @@ export default class BugReportDialog extends React.Component<IProps, IState> {
|
|||
downloadProgress: null,
|
||||
};
|
||||
this.unmounted = false;
|
||||
|
||||
// Get all of the extra info dumped to the console when someone is about
|
||||
// to send debug logs. Since this is a fire and forget action, we do
|
||||
// this when the bug report dialog is opened instead of when we submit
|
||||
// logs because we have no signal to know when all of the various
|
||||
// components have finished logging. Someone could potentially send logs
|
||||
// before we fully dump everything but it's probably unlikely.
|
||||
defaultDispatcher.dispatch({
|
||||
action: Action.DumpDebugLogs,
|
||||
});
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
|
|
|
@ -1292,6 +1292,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
"data-has-reply": !!replyChain,
|
||||
"data-layout": this.props.layout,
|
||||
"data-self": isOwnEvent,
|
||||
"data-event-id": this.props.mxEvent.getId(),
|
||||
"onMouseEnter": () => this.setState({ hover: true }),
|
||||
"onMouseLeave": () => this.setState({ hover: false }),
|
||||
}, [
|
||||
|
@ -1438,6 +1439,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
|
|||
"data-scroll-tokens": scrollToken,
|
||||
"data-layout": this.props.layout,
|
||||
"data-self": isOwnEvent,
|
||||
"data-event-id": this.props.mxEvent.getId(),
|
||||
"data-has-reply": !!replyChain,
|
||||
"onMouseEnter": () => this.setState({ hover: true }),
|
||||
"onMouseLeave": () => this.setState({ hover: false }),
|
||||
|
|
|
@ -306,4 +306,11 @@ export enum Action {
|
|||
* Opens a dialog to add an existing object to a space. Used with a OpenAddExistingToSpaceDialogPayload.
|
||||
*/
|
||||
OpenAddToExistingSpaceDialog = "open_add_to_existing_space_dialog",
|
||||
|
||||
/**
|
||||
* Let components know that they should log any useful debugging information
|
||||
* because we're probably about to send bug report which includes all of the
|
||||
* logs. Fires with no payload.
|
||||
*/
|
||||
DumpDebugLogs = "dump_debug_logs",
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue