[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 }
: 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 = null;
this.#altTextTooltip.classList.add("show");
this.#editor._uiManager._eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "editing",
subtype: this.#editor.editorType,
data: {
action: "alt_text_tooltip",
},
},
this.#editor._reportTelemetry({
action: "alt_text_tooltip",
});
}, DELAY_TO_SHOW_TOOLTIP);
});

View File

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

View File

@ -72,6 +72,8 @@ class AnnotationEditor {
#prevDragY = 0;
#telemetryTimeouts = null;
_initialOptions = Object.create(null);
_uiManager = null;
@ -90,6 +92,11 @@ class AnnotationEditor {
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() {
const resize = AnnotationEditor.prototype._resizeWithKeyboard;
const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
@ -1323,6 +1330,12 @@ class AnnotationEditor {
}
this.#stopResizing();
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() {
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.

View File

@ -52,6 +52,8 @@ class HighlightEditor extends AnnotationEditor {
#thickness;
#methodOfCreation = "";
static _defaultColor = null;
static _defaultOpacity = 1;
@ -76,6 +78,7 @@ class HighlightEditor extends AnnotationEditor {
this.#thickness = params.thickness || HighlightEditor._defaultThickness;
this.#opacity = params.opacity || HighlightEditor._defaultOpacity;
this.#boxes = params.boxes || null;
this.#methodOfCreation = params.methodOfCreation || "";
this._isDraggable = false;
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() {
const outliner = new Outliner(this.#boxes, /* borderWidth = */ 0.001);
this.#highlightOutlines = outliner.getOutlines();
@ -274,6 +305,14 @@ class HighlightEditor extends AnnotationEditor {
overwriteIfSameType: 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,
keepUndo: true,
});
this._reportTelemetry(
{ action: "thickness_changed", thickness },
/* mustWait = */ true
);
}
/** @inheritdoc */
@ -349,6 +392,9 @@ class HighlightEditor extends AnnotationEditor {
remove() {
super.remove();
this.#cleanDrawLayer();
this._reportTelemetry({
action: "deleted",
});
}
/** @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
// count the number of times an image is added to the page whatever the way
// is.
this._uiManager._eventBus.dispatch("reporttelemetry", {
source: this,
details: {
type: "editing",
subtype: this.editorType,
data: {
action: "inserted_image",
},
},
this._reportTelemetry({
action: "inserted_image",
});
this.addAltTextButton();
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) {
this.#mainHighlightColorPicker = colorPicker;
}
@ -932,7 +942,7 @@ class AnnotationEditorUIManager {
this.viewParameters.rotation = pagesRotation;
}
highlightSelection() {
highlightSelection(methodOfCreation = "") {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {
return;
@ -953,7 +963,10 @@ class AnnotationEditorUIManager {
}
for (const layer of this.#allLayers.values()) {
if (layer.hasTextLayer(textLayer)) {
layer.createAndAddNewEditor({ x: 0, y: 0 }, false, { boxes });
layer.createAndAddNewEditor({ x: 0, y: 0 }, false, {
methodOfCreation,
boxes,
});
break;
}
}
@ -1020,7 +1033,7 @@ class AnnotationEditorUIManager {
window.removeEventListener("pointerup", pointerup);
window.removeEventListener("blur", pointerup);
if (e.type === "pointerup") {
this.highlightSelection();
this.highlightSelection("main_toolbar");
}
};
window.addEventListener("pointerup", pointerup);
@ -1050,7 +1063,7 @@ class AnnotationEditorUIManager {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
this.highlightSelection();
this.highlightSelection("main_toolbar");
}
if (!this.hasSelection) {
return;
@ -1240,7 +1253,7 @@ class AnnotationEditorUIManager {
this.isShiftKeyDown = false;
if (this.#highlightWhenShiftUp) {
this.#highlightWhenShiftUp = false;
this.highlightSelection();
this.highlightSelection("main_toolbar");
}
}
}
@ -1249,15 +1262,18 @@ class AnnotationEditorUIManager {
* Execute an action for a given name.
* For example, the user can click on the "Undo" entry in the context menu
* and it'll trigger the undo action.
* @param {Object} details
*/
onEditingAction(details) {
if (
["undo", "redo", "delete", "selectAll", "highlightSelection"].includes(
details.name
)
) {
this[details.name]();
onEditingAction({ name }) {
switch (name) {
case "undo":
case "redo":
case "delete":
case "selectAll":
this[name]();
break;
case "highlightSelection":
this.highlightSelection("context_menu");
break;
}
}

View File

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

View File

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