[Editor] Add some telemetry for the highlight feature (bug 1866437)

This commit is contained in:
Calixte Denizet 2024-02-27 21:44:13 +01:00
parent e42b114e80
commit 65342d2bee
9 changed files with 186 additions and 51 deletions

View File

@ -212,6 +212,41 @@ class AnnotationStorage {
? { map, hash: hash.hexdigest(), transfer } ? { map, hash: hash.hexdigest(), transfer }
: SerializableEmpty; : SerializableEmpty;
} }
get editorStats() {
const stats = Object.create(null);
const typeToEditor = new Map();
for (const value of this.#storage.values()) {
if (!(value instanceof AnnotationEditor)) {
continue;
}
const editorStats = value.telemetryFinalData;
if (!editorStats) {
continue;
}
const { type } = editorStats;
if (!typeToEditor.has(type)) {
typeToEditor.set(type, Object.getPrototypeOf(value).constructor);
}
const map = (stats[type] ||= new Map());
for (const [key, val] of Object.entries(editorStats)) {
if (key === "type") {
continue;
}
let counters = map.get(key);
if (!counters) {
counters = new Map();
map.set(key, counters);
}
const count = counters.get(val) ?? 0;
counters.set(val, count + 1);
}
}
for (const [type, editor] of typeToEditor) {
stats[type] = editor.computeTelemetryFinalData(stats[type]);
}
return stats;
}
} }
/** /**

View File

@ -146,15 +146,8 @@ class AltText {
this.#altTextTooltipTimeout = setTimeout(() => { this.#altTextTooltipTimeout = setTimeout(() => {
this.#altTextTooltipTimeout = null; this.#altTextTooltipTimeout = null;
this.#altTextTooltip.classList.add("show"); this.#altTextTooltip.classList.add("show");
this.#editor._uiManager._eventBus.dispatch("reporttelemetry", { this.#editor._reportTelemetry({
source: this, action: "alt_text_tooltip",
details: {
type: "editing",
subtype: this.#editor.editorType,
data: {
action: "alt_text_tooltip",
},
},
}); });
}, DELAY_TO_SHOW_TOOLTIP); }, DELAY_TO_SHOW_TOOLTIP);
}); });

View File

@ -471,6 +471,7 @@ class AnnotationEditorLayer {
editor.fixAndSetPosition(); editor.fixAndSetPosition();
editor.onceAdded(); editor.onceAdded();
this.#uiManager.addToAnnotationStorage(editor); this.#uiManager.addToAnnotationStorage(editor);
editor._reportTelemetry(editor.telemetryInitialData);
} }
moveEditorInDOM(editor) { moveEditorInDOM(editor) {

View File

@ -72,6 +72,8 @@ class AnnotationEditor {
#prevDragY = 0; #prevDragY = 0;
#telemetryTimeouts = null;
_initialOptions = Object.create(null); _initialOptions = Object.create(null);
_uiManager = null; _uiManager = null;
@ -90,6 +92,11 @@ class AnnotationEditor {
static _zIndex = 1; static _zIndex = 1;
// Time to wait (in ms) before sending the telemetry data.
// We wait a bit to avoid sending too many requests when changing something
// like the thickness of a line.
static _telemetryTimeout = 1000;
static get _resizerKeyboardManager() { static get _resizerKeyboardManager() {
const resize = AnnotationEditor.prototype._resizeWithKeyboard; const resize = AnnotationEditor.prototype._resizeWithKeyboard;
const small = AnnotationEditorUIManager.TRANSLATE_SMALL; const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
@ -1323,6 +1330,12 @@ class AnnotationEditor {
} }
this.#stopResizing(); this.#stopResizing();
this.removeEditToolbar(); this.removeEditToolbar();
if (this.#telemetryTimeouts) {
for (const timeout of this.#telemetryTimeouts.values()) {
clearTimeout(timeout);
}
this.#telemetryTimeouts = null;
}
} }
/** /**
@ -1598,6 +1611,50 @@ class AnnotationEditor {
static canCreateNewEmptyEditor() { static canCreateNewEmptyEditor() {
return true; return true;
} }
/**
* Get the data to report to the telemetry when the editor is added.
* @returns {Object}
*/
get telemetryInitialData() {
return { action: "added" };
}
/**
* The telemetry data to use when saving/printing.
* @returns {Object|null}
*/
get telemetryFinalData() {
return null;
}
_reportTelemetry(data, mustWait = false) {
if (mustWait) {
this.#telemetryTimeouts ||= new Map();
const { action } = data;
let timeout = this.#telemetryTimeouts.get(action);
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(() => {
this._reportTelemetry(data);
this.#telemetryTimeouts.delete(action);
if (this.#telemetryTimeouts.size === 0) {
this.#telemetryTimeouts = null;
}
}, AnnotationEditor._telemetryTimeout);
this.#telemetryTimeouts.set(action, timeout);
return;
}
data.type ||= this.editorType;
this._uiManager._eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "editing",
data,
},
});
}
} }
// This class is used to fake an editor which has been deleted. // This class is used to fake an editor which has been deleted.

View File

@ -52,6 +52,8 @@ class HighlightEditor extends AnnotationEditor {
#thickness; #thickness;
#methodOfCreation = "";
static _defaultColor = null; static _defaultColor = null;
static _defaultOpacity = 1; static _defaultOpacity = 1;
@ -76,6 +78,7 @@ class HighlightEditor extends AnnotationEditor {
this.#thickness = params.thickness || HighlightEditor._defaultThickness; this.#thickness = params.thickness || HighlightEditor._defaultThickness;
this.#opacity = params.opacity || HighlightEditor._defaultOpacity; this.#opacity = params.opacity || HighlightEditor._defaultOpacity;
this.#boxes = params.boxes || null; this.#boxes = params.boxes || null;
this.#methodOfCreation = params.methodOfCreation || "";
this._isDraggable = false; this._isDraggable = false;
if (params.highlightId > -1) { if (params.highlightId > -1) {
@ -89,6 +92,34 @@ class HighlightEditor extends AnnotationEditor {
} }
} }
/** @inheritdoc */
get telemetryInitialData() {
return {
action: "added",
type: this.#telemetryType,
color: this._uiManager.highlightColorNames.get(this.color),
thickness: this.#thickness,
methodOfCreation: this.#methodOfCreation,
};
}
/** @inheritdoc */
get telemetryFinalData() {
return {
type: this.#telemetryType,
color: this._uiManager.highlightColorNames.get(this.color),
};
}
static computeTelemetryFinalData(data) {
// We want to know how many colors have been used.
return { numberOfColors: data.get("color").size };
}
get #telemetryType() {
return this.#isFreeHighlight ? "free_highlight" : "highlight";
}
#createOutlines() { #createOutlines() {
const outliner = new Outliner(this.#boxes, /* borderWidth = */ 0.001); const outliner = new Outliner(this.#boxes, /* borderWidth = */ 0.001);
this.#highlightOutlines = outliner.getOutlines(); this.#highlightOutlines = outliner.getOutlines();
@ -274,6 +305,14 @@ class HighlightEditor extends AnnotationEditor {
overwriteIfSameType: true, overwriteIfSameType: true,
keepUndo: true, keepUndo: true,
}); });
this._reportTelemetry(
{
action: "color_changed",
color: this._uiManager.highlightColorNames.get(color),
},
/* mustWait = */ true
);
} }
/** /**
@ -295,6 +334,10 @@ class HighlightEditor extends AnnotationEditor {
overwriteIfSameType: true, overwriteIfSameType: true,
keepUndo: true, keepUndo: true,
}); });
this._reportTelemetry(
{ action: "thickness_changed", thickness },
/* mustWait = */ true
);
} }
/** @inheritdoc */ /** @inheritdoc */
@ -349,6 +392,9 @@ class HighlightEditor extends AnnotationEditor {
remove() { remove() {
super.remove(); super.remove();
this.#cleanDrawLayer(); this.#cleanDrawLayer();
this._reportTelemetry({
action: "deleted",
});
} }
/** @inheritdoc */ /** @inheritdoc */

View File

@ -326,15 +326,8 @@ class StampEditor extends AnnotationEditor {
// There are multiple ways to add an image to the page, so here we just // There are multiple ways to add an image to the page, so here we just
// count the number of times an image is added to the page whatever the way // count the number of times an image is added to the page whatever the way
// is. // is.
this._uiManager._eventBus.dispatch("reporttelemetry", { this._reportTelemetry({
source: this, action: "inserted_image",
details: {
type: "editing",
subtype: this.editorType,
data: {
action: "inserted_image",
},
},
}); });
this.addAltTextButton(); this.addAltTextButton();
if (this.#bitmapFileName) { if (this.#bitmapFileName) {

View File

@ -871,6 +871,16 @@ class AnnotationEditorUIManager {
); );
} }
get highlightColorNames() {
return shadow(
this,
"highlightColorNames",
this.highlightColors
? new Map(Array.from(this.highlightColors, e => e.reverse()))
: null
);
}
setMainHighlightColorPicker(colorPicker) { setMainHighlightColorPicker(colorPicker) {
this.#mainHighlightColorPicker = colorPicker; this.#mainHighlightColorPicker = colorPicker;
} }
@ -932,7 +942,7 @@ class AnnotationEditorUIManager {
this.viewParameters.rotation = pagesRotation; this.viewParameters.rotation = pagesRotation;
} }
highlightSelection() { highlightSelection(methodOfCreation = "") {
const selection = document.getSelection(); const selection = document.getSelection();
if (!selection || selection.isCollapsed) { if (!selection || selection.isCollapsed) {
return; return;
@ -953,7 +963,10 @@ class AnnotationEditorUIManager {
} }
for (const layer of this.#allLayers.values()) { for (const layer of this.#allLayers.values()) {
if (layer.hasTextLayer(textLayer)) { if (layer.hasTextLayer(textLayer)) {
layer.createAndAddNewEditor({ x: 0, y: 0 }, false, { boxes }); layer.createAndAddNewEditor({ x: 0, y: 0 }, false, {
methodOfCreation,
boxes,
});
break; break;
} }
} }
@ -1020,7 +1033,7 @@ class AnnotationEditorUIManager {
window.removeEventListener("pointerup", pointerup); window.removeEventListener("pointerup", pointerup);
window.removeEventListener("blur", pointerup); window.removeEventListener("blur", pointerup);
if (e.type === "pointerup") { if (e.type === "pointerup") {
this.highlightSelection(); this.highlightSelection("main_toolbar");
} }
}; };
window.addEventListener("pointerup", pointerup); window.addEventListener("pointerup", pointerup);
@ -1050,7 +1063,7 @@ class AnnotationEditorUIManager {
this.isShiftKeyDown = false; this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) { if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false; this.#highlightWhenShiftUp = false;
this.highlightSelection(); this.highlightSelection("main_toolbar");
} }
if (!this.hasSelection) { if (!this.hasSelection) {
return; return;
@ -1240,7 +1253,7 @@ class AnnotationEditorUIManager {
this.isShiftKeyDown = false; this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) { if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false; this.#highlightWhenShiftUp = false;
this.highlightSelection(); this.highlightSelection("main_toolbar");
} }
} }
} }
@ -1249,15 +1262,18 @@ class AnnotationEditorUIManager {
* Execute an action for a given name. * Execute an action for a given name.
* For example, the user can click on the "Undo" entry in the context menu * For example, the user can click on the "Undo" entry in the context menu
* and it'll trigger the undo action. * and it'll trigger the undo action.
* @param {Object} details
*/ */
onEditingAction(details) { onEditingAction({ name }) {
if ( switch (name) {
["undo", "redo", "delete", "selectAll", "highlightSelection"].includes( case "undo":
details.name case "redo":
) case "delete":
) { case "selectAll":
this[details.name](); this[name]();
break;
case "highlightSelection":
this.highlightSelection("context_menu");
break;
} }
} }

View File

@ -248,17 +248,12 @@ class AltTextManager {
} }
#close() { #close() {
this.#eventBus.dispatch("reporttelemetry", { this.#currentEditor._reportTelemetry(
source: this, this.#telemetryData || {
details: { action: "alt_text_cancel",
type: "editing", alt_text_keyboard: !this.#hasUsedPointer,
subtype: this.#currentEditor.editorType, }
data: this.#telemetryData || { );
action: "alt_text_cancel",
alt_text_keyboard: !this.#hasUsedPointer,
},
},
});
this.#telemetryData = null; this.#telemetryData = null;
this.#removeOnClickListeners(); this.#removeOnClickListeners();

View File

@ -1152,7 +1152,10 @@ const PDFViewerApplication = {
if (this._hasAnnotationEditors) { if (this._hasAnnotationEditors) {
this.externalServices.reportTelemetry({ this.externalServices.reportTelemetry({
type: "editing", type: "editing",
data: { type: "save" }, data: {
type: "save",
stats: this.pdfDocument?.annotationStorage.editorStats,
},
}); });
} }
}, },
@ -1727,13 +1730,6 @@ const PDFViewerApplication = {
annotationStorage.onAnnotationEditor = typeStr => { annotationStorage.onAnnotationEditor = typeStr => {
this._hasAnnotationEditors = !!typeStr; this._hasAnnotationEditors = !!typeStr;
this.setTitle(); this.setTitle();
if (typeStr) {
this.externalServices.reportTelemetry({
type: "editing",
data: { type: typeStr },
});
}
}; };
}, },
@ -1857,7 +1853,10 @@ const PDFViewerApplication = {
if (this._hasAnnotationEditors) { if (this._hasAnnotationEditors) {
this.externalServices.reportTelemetry({ this.externalServices.reportTelemetry({
type: "editing", type: "editing",
data: { type: "print" }, data: {
type: "print",
stats: this.pdfDocument?.annotationStorage.editorStats,
},
}); });
} }
}, },