Patch over slow debug log rendering

This commit is contained in:
Evan Hahn 2021-01-14 12:01:48 -06:00 committed by Scott Nonnenberg
parent d1355d5874
commit 7884f4033e
3 changed files with 135 additions and 29 deletions

View file

@ -8,6 +8,22 @@
(function () {
window.Whisper = window.Whisper || {};
// This enum-like object describes the load state of `DebugLogView`. It's designed to be
// unidirectional; `NotStarted` → `Started` → `LogsFetchedButNotInTextarea`, etc.
const LoadState = {
NotStarted: 0,
Started: 1,
LogsFetchedButNotInTextarea: 2,
PuttingLogsInTextarea: 3,
LogsInTextarea: 4,
};
Whisper.LoadingFullLogsToast = Whisper.ToastView.extend({
render_attributes() {
return { toastMessage: i18n('loading') };
},
});
Whisper.LinkedCopiedToast = Whisper.ToastView.extend({
render_attributes() {
return { toastMessage: i18n('debugLogLinkCopied') };
@ -36,19 +52,40 @@
Whisper.ToastView.show(Whisper.LinkedCopiedToast, document.body);
},
});
/**
* The bulk of the logic in this view involves grabbing the logs from disk and putting
* them in a `<textarea>`. The first part isn't instant but is reasonably fast; setting
* the textarea's `value` takes a long time.
*
* After loading the logs into memory, we only put a small number of lines into the
* textarea. If the user clicks or scrolls the textarea, we pull the full logs, which
* can cause the system to lock up for a bit.
*
* Ideally, we'd only show a sampling of the logs and allow the user to download and
* edit them in their own editor. This is mostly a stopgap solution.
*/
Whisper.DebugLogView = Whisper.View.extend({
templateName: 'debug-log',
className: 'debug-log modal',
initialize() {
this.render();
this.$('textarea').val(i18n('loading'));
// eslint-disable-next-line more/no-then
window.log.fetch().then(text => {
this.$('textarea').val(text);
});
this.textarea = this.$('.textarea').get(0);
if (!this.textarea) {
throw new Error('textarea not found');
}
this.textarea.setAttribute('readonly', '');
this.loadState = LoadState.NotStarted;
this.putFullLogsInTextareaPlease = false;
this.fetchLogs();
},
events: {
'click .textarea': 'putFullLogsInTextarea',
'scroll .textarea': 'putFullLogsInTextarea',
'wheel .textarea': 'putFullLogsInTextarea',
'click .submit': 'submit',
'click .close': 'close',
},
@ -59,17 +96,86 @@
close: i18n('gotIt'),
debugLogExplanation: i18n('debugLogExplanation'),
},
async fetchLogs() {
if (this.loadState !== LoadState.NotStarted) {
return;
}
this.loadState = LoadState.Started;
this.textarea.value = i18n('loading');
this.$('.submit').attr('disabled', 'disabled');
this.logText = await window.log.fetch();
this.loadState = LoadState.LogsFetchedButNotInTextarea;
// This number is somewhat arbitrary; we want to show enough that it's clear that
// we need to scroll, but not so many that things get slow.
const linesToShow = Math.ceil(Math.min(window.innerHeight, 2000) / 5);
this.textarea.value = this.logText
.split(/\n/g)
.slice(0, linesToShow)
.concat(['', i18n('loading')])
.join('\n');
this.$('.submit').removeAttr('disabled');
if (this.putFullLogsInTextareaPlease) {
this.putFullLogsInTextarea();
}
},
putFullLogsInTextarea() {
switch (this.loadState) {
case LoadState.NotStarted:
case LoadState.Started:
this.putFullLogsInTextareaPlease = true;
break;
case LoadState.LogsInTextarea:
case LoadState.PuttingLogsInTextarea:
break;
case LoadState.LogsFetchedButNotInTextarea:
if (!this.logText) {
throw new Error('Expected log text to be present');
}
this.loadState = LoadState.PuttingLogsInTextarea;
Whisper.ToastView.show(Whisper.LoadingFullLogsToast, document.body);
setTimeout(() => {
this.textarea.value = this.logText;
this.textarea.removeAttribute('readonly');
this.loadState = LoadState.LogsInTextarea;
}, 0);
break;
default:
// When we can, we should make this throw a `missingCaseError`.
break;
}
},
close() {
window.closeDebugLog();
},
async submit(e) {
e.preventDefault();
const text = this.$('textarea').val();
let text;
switch (this.loadState) {
case LoadState.NotStarted:
case LoadState.Started:
return;
case LoadState.LogsFetchedButNotInTextarea:
text = this.logText;
break;
case LoadState.LogsInTextarea:
text = this.textarea.value;
break;
default:
// When we can, we should make this throw a `missingCaseError`.
return;
}
if (text.length === 0) {
return;
}
this.$('.buttons, textarea').remove();
this.$('.buttons, .textarea').remove();
this.$('.result').addClass('loading');
try {