(core) Add additional telemetry events

Summary: The new events capture usage of forms, widgets, access rules, and onboarding tours and tips.

Test Plan: Manual.

Reviewers: dsagal

Reviewed By: dsagal

Subscribers: dsagal

Differential Revision: https://phab.getgrist.com/D4189
This commit is contained in:
George Gevoian
2024-02-13 12:49:00 -05:00
parent 7f9e2817d1
commit b8f32d1784
25 changed files with 483 additions and 55 deletions

View File

@@ -1,4 +1,5 @@
import {showBehavioralPrompt} from 'app/client/components/modals';
import {logTelemetryEvent} from 'app/client/lib/telemetry';
import {AppModel} from 'app/client/models/AppModel';
import {getUserPrefObs} from 'app/client/models/UserPrefs';
import {GristBehavioralPrompts} from 'app/client/ui/GristTooltips';
@@ -159,6 +160,8 @@ export class BehavioralPromptsManager extends Disposable {
});
dom.onElem(refElement, 'click', () => close());
dom.onDisposeElem(refElement, () => close());
logTelemetryEvent('viewedTip', {full: {tipName: prompt}});
}
private _showNextQueuedTip() {

View File

@@ -2,7 +2,7 @@ import {cssBannerLink} from 'app/client/components/Banner';
import {DocPageModel} from 'app/client/models/DocPageModel';
import {urlState} from 'app/client/models/gristUrlState';
import {docListHeader} from 'app/client/ui/DocMenuCss';
import {GristTooltips, TooltipContentFunc} from 'app/client/ui/GristTooltips';
import {Tooltip} from 'app/client/ui/GristTooltips';
import {withInfoTooltip} from 'app/client/ui/tooltips';
import {mediaXSmall, theme} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
@@ -81,7 +81,7 @@ export class DocumentUsage extends Disposable {
maximumValue: maxValue ?? DEFAULT_MAX_DATA_SIZE,
unit: 'MB',
shouldHideLimits: maxValue === undefined,
tooltipContentFunc: GristTooltips.dataSize,
tooltip: 'dataSize',
formatValue: (val) => {
// To display a nice, round number for `maximumValue`, we first convert
// to KiBs (base-2), and then convert to MBs (base-10). Normally, we wouldn't
@@ -271,7 +271,7 @@ interface MetricOptions {
// If true, limits will always be hidden, even if `maximumValue` is a positive number.
shouldHideLimits?: boolean;
// Shows an icon next to the metric name that displays a tooltip on hover.
tooltipContentFunc?: TooltipContentFunc;
tooltip?: Tooltip;
formatValue?(value: number): string;
}
@@ -281,14 +281,11 @@ interface MetricOptions {
* close `currentValue` is to hitting `maximumValue`.
*/
function buildUsageMetric(options: MetricOptions, ...domArgs: DomElementArg[]) {
const {name, tooltipContentFunc} = options;
const {name, tooltip} = options;
return cssUsageMetric(
cssMetricName(
tooltipContentFunc
? withInfoTooltip(
cssOverflowableText(name, testId('name')),
tooltipContentFunc()
)
tooltip
? withInfoTooltip(cssOverflowableText(name, testId('name')), tooltip)
: cssOverflowableText(name, testId('name')),
),
buildUsageProgressBar(options),

View File

@@ -11,6 +11,7 @@ import {Disposable} from 'app/client/lib/dispose';
import {AsyncComputed, makeTestId, stopEvent} from 'app/client/lib/domUtils';
import {makeT} from 'app/client/lib/localization';
import {localStorageBoolObs} from 'app/client/lib/localStorageObs';
import {logTelemetryEvent} from 'app/client/lib/telemetry';
import DataTableModel from 'app/client/models/DataTableModel';
import {ViewFieldRec, ViewSectionRec} from 'app/client/models/DocModel';
import {ShareRec} from 'app/client/models/entities/ShareRec';
@@ -514,6 +515,13 @@ export class FormView extends Disposable {
throw ex;
}
}
logTelemetryEvent('publishedForm', {
full: {
docIdDigest: this.gristDoc.docId(),
},
});
await this.gristDoc.docModel.docData.bundleActions('Publish form', async () => {
if (!validShare) {
const shareRef = await this.gristDoc.docModel.docData.sendAction([
@@ -573,6 +581,12 @@ export class FormView extends Disposable {
}
private async _unpublishForm() {
logTelemetryEvent('unpublishedForm', {
full: {
docIdDigest: this.gristDoc.docId(),
},
});
await this.gristDoc.docModel.docData.bundleActions('Unpublish form', async () => {
this.viewSection.shareOptionsObj.update({
publish: false,

View File

@@ -47,9 +47,10 @@ import {DocTutorial} from 'app/client/ui/DocTutorial';
import {DocSettingsPage} from 'app/client/ui/DocumentSettings';
import {isTourActive} from "app/client/ui/OnBoardingPopups";
import {DefaultPageWidget, IPageWidget, toPageWidget} from 'app/client/ui/PageWidgetPicker';
import {linkFromId, selectBy} from 'app/client/ui/selectBy';
import {linkFromId, NoLink, selectBy} from 'app/client/ui/selectBy';
import {WebhookPage} from 'app/client/ui/WebhookPage';
import {startWelcomeTour} from 'app/client/ui/WelcomeTour';
import {getTelemetryWidgetTypeFromPageWidget} from 'app/client/ui/widgetTypesMap';
import {PlayerState, YouTubePlayer} from 'app/client/ui/YouTubePlayer';
import {isNarrowScreen, mediaSmall, mediaXSmall, testId, theme} from 'app/client/ui2018/cssVars';
import {IconName} from 'app/client/ui2018/IconList';
@@ -882,6 +883,13 @@ export class GristDoc extends DisposableWithEvents {
return;
}
}
const widgetType = getTelemetryWidgetTypeFromPageWidget(val);
logTelemetryEvent('addedWidget', {full: {docIdDigest: this.docId(), widgetType}});
if (val.link !== NoLink) {
logTelemetryEvent('linkedWidget', {full: {docIdDigest: this.docId(), widgetType}});
}
const res: {sectionRef: number} = await docData.bundleActions(
t("Added new linked section to view {{viewName}}", {viewName}),
() => this.addWidgetToPageImpl(val, tableId ?? null)
@@ -932,6 +940,14 @@ export class GristDoc extends DisposableWithEvents {
* Adds a new page (aka: view) with a single view section (aka: page widget) described by `val`.
*/
public async addNewPage(val: IPageWidget) {
logTelemetryEvent('addedPage', {full: {docIdDigest: this.docId()}});
logTelemetryEvent('addedWidget', {
full: {
docIdDigest: this.docId(),
widgetType: getTelemetryWidgetTypeFromPageWidget(val),
},
});
if (val.table === 'New Table') {
const name = await this._promptForName();
if (name === undefined) {

View File

@@ -4,12 +4,14 @@ import {DocumentUsage} from 'app/client/components/DocumentUsage';
import {GristDoc} from 'app/client/components/GristDoc';
import {printViewSection} from 'app/client/components/Printing';
import {ViewSectionHelper} from 'app/client/components/ViewLayout';
import {logTelemetryEvent} from 'app/client/lib/telemetry';
import {mediaSmall, theme, vars} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {Computed, Disposable, dom, fromKo, makeTestId, Observable, styled} from 'grainjs';
import {reportError} from 'app/client/models/errors';
import {ViewSectionRec} from 'app/client/models/DocModel';
import {buildViewSectionDom} from 'app/client/components/buildViewSectionDom';
import {getTelemetryWidgetTypeFromVS} from 'app/client/ui/widgetTypesMap';
const testId = makeTestId('test-raw-data-');
@@ -82,6 +84,10 @@ export class RawDataPopup extends Disposable {
if (this._viewSection.isRaw.peek()) {
throw new Error("Can't delete a raw section");
}
const widgetType = getTelemetryWidgetTypeFromVS(this._viewSection);
logTelemetryEvent('deletedWidget', {full: {docIdDigest: this._gristDoc.docId(), widgetType}});
this._gristDoc.docData.sendAction(['RemoveViewSection', this._viewSection.id.peek()]).catch(reportError);
},
};

View File

@@ -14,8 +14,10 @@ import {LayoutTray} from 'app/client/components/LayoutTray';
import {printViewSection} from 'app/client/components/Printing';
import {Delay} from 'app/client/lib/Delay';
import {createObsArray} from 'app/client/lib/koArrayWrap';
import {logTelemetryEvent} from 'app/client/lib/telemetry';
import {ViewRec, ViewSectionRec} from 'app/client/models/DocModel';
import {reportError} from 'app/client/models/errors';
import {getTelemetryWidgetTypeFromVS} from 'app/client/ui/widgetTypesMap';
import {isNarrowScreen, mediaSmall, testId, theme} from 'app/client/ui2018/cssVars';
import {icon} from 'app/client/ui2018/icons';
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
@@ -279,6 +281,14 @@ export class ViewLayout extends DisposableWithEvents implements IDomComponent {
// more than one viewsection in the view.
public removeViewSection(viewSectionRowId: number) {
this.maximized.set(null);
const viewSection = this.viewModel.viewSections().all().find(s => s.getRowId() === viewSectionRowId);
if (!viewSection) {
throw new Error(`Section not found: ${viewSectionRowId}`);
}
const widgetType = getTelemetryWidgetTypeFromVS(viewSection);
logTelemetryEvent('deletedWidget', {full: {docIdDigest: this.gristDoc.docId(), widgetType}});
this.gristDoc.docData.sendAction(['RemoveViewSection', viewSectionRowId]).catch(reportError);
}

View File

@@ -1,4 +1,5 @@
import { GristDoc } from 'app/client/components/GristDoc';
import { logTelemetryEvent } from 'app/client/lib/telemetry';
import { ViewFieldRec, ViewSectionRec } from 'app/client/models/DocModel';
import { cssInput } from 'app/client/ui/cssInput';
import { cssField, cssLabel } from 'app/client/ui/MakeCopyMenu';
@@ -43,6 +44,8 @@ async function makeDuplicate(gristDoc: GristDoc, pageId: number, pageName: strin
await gristDoc.docData.bundleActions(
t("Duplicate page {{pageName}}", {pageName}),
async () => {
logTelemetryEvent('addedPage', {full: {docIdDigest: gristDoc.docId()}});
// create new view and new sections
const results = await createNewViewSections(gristDoc.docData, viewSections);
viewRef = results[0].viewRef;