(core) Add welcomeQuestionsSubmitted telemetry event

Summary:
The new event captures responses to the welcome questionnaire.

Responses are also still sent to the special Grist document configured with
the DOC_ID_NEW_USER_INFO variable.

Test Plan: Tested manually.

Reviewers: jarek

Reviewed By: jarek

Differential Revision: https://phab.getgrist.com/D4034
This commit is contained in:
George Gevoian 2023-09-09 17:01:53 -04:00
parent f659f3655d
commit 76e822eb23
4 changed files with 51 additions and 5 deletions

View File

@ -27,9 +27,8 @@ export function showWelcomeQuestions(userPrefsObs: Observable<UserPrefs>) {
const showQuestions = getUserPrefObs(userPrefsObs, 'showNewUserQuestions');
async function onConfirm() {
const selected = choices.filter((c, i) => selection[i].get()).map(c => c.textKey);
const use_cases = ['L', ...selected]; // Format to populate a ChoiceList column
const use_other = selected.includes("Other") ? otherText.get() : '';
const use_cases = choices.filter((c, i) => selection[i].get()).map(c => c.textKey);
const use_other = use_cases.includes("Other") ? otherText.get() : '';
const submitUrl = new URL(window.location.href);
submitUrl.pathname = '/welcome/info';

View File

@ -862,6 +862,25 @@ export const TelemetryContracts: TelemetryContracts = {
},
},
},
welcomeQuestionsSubmitted: {
description: 'Triggered when the welcome questionnaire is submitted.',
minimumTelemetryLevel: Level.full,
retentionPeriod: 'indefinitely',
metadataContracts: {
useCases: {
description: 'The selected use cases.',
dataType: 'string[]',
},
useOther: {
description: 'The value of the Other use case.',
dataType: 'string',
},
userId: {
description: 'The id of the user that triggered this event.',
dataType: 'number',
},
},
},
};
type TelemetryContracts = Record<TelemetryEvent, TelemetryEventContract>;
@ -891,6 +910,7 @@ export const TelemetryEvents = StringUnion(
'tutorialProgressChanged',
'tutorialRestarted',
'watchedVideoTour',
'welcomeQuestionsSubmitted',
);
export type TelemetryEvent = typeof TelemetryEvents.type;

View File

@ -53,7 +53,7 @@ import {addPluginEndpoints, limitToPlugins} from 'app/server/lib/PluginEndpoint'
import {PluginManager} from 'app/server/lib/PluginManager';
import * as ProcessMonitor from 'app/server/lib/ProcessMonitor';
import {adaptServerUrl, getOrgUrl, getOriginUrl, getScope, isDefaultUser, optStringParam,
RequestWithGristInfo, sendOkReply, stringParam, TEST_HTTPS_OFFSET,
RequestWithGristInfo, sendOkReply, stringArrayParam, stringParam, TEST_HTTPS_OFFSET,
trustOrigin} from 'app/server/lib/requestUtils';
import {ISendAppPageOptions, makeGristConfig, makeMessagePage, makeSendAppPage} from 'app/server/lib/sendAppPage';
import {getDatabaseUrl, listenPromise} from 'app/server/lib/serverUtils';
@ -1304,12 +1304,28 @@ export class FlexServer implements GristServer {
this.app.post('/welcome/info', ...middleware, expressWrap(async (req, resp, next) => {
const userId = getUserId(req);
const user = getUser(req);
const row = {...req.body, UserID: userId, Name: user.name, Email: user.loginEmail};
const useCases = stringArrayParam(req.body.use_cases, 'use_cases');
const useOther = stringParam(req.body.use_other, 'use_other');
const row = {
UserID: userId,
Name: user.name,
Email: user.loginEmail,
use_cases: ['L', ...useCases],
use_other: useOther,
};
this._recordNewUserInfo(row)
.catch(e => {
// If we failed to record, at least log the data, so we could potentially recover it.
log.rawWarn(`Failed to record new user info: ${e.message}`, {newUserQuestions: row});
});
this.getTelemetry().logEvent('welcomeQuestionsSubmitted', {
full: {
userId,
useCases,
useOther,
},
})
.catch(e => log.error('failed to log telemetry event welcomeQuestionsSubmitted', e));
resp.status(200).send();
}), jsonErrorHandler); // Add a final error handler that reports errors as JSON.

View File

@ -282,6 +282,17 @@ export function stringParam(p: any, name: string, options: StringParamOptions =
return p;
}
export function stringArrayParam(p: any, name: string): string[] {
if (!Array.isArray(p)) {
throw new ApiError(`${name} parameter should be an array: ${p}`, 400);
}
if (p.some(el => typeof el !== 'string')) {
throw new ApiError(`${name} parameter should be a string array: ${p}`, 400);
}
return p;
}
export function optIntegerParam(p: any, name: string): number|undefined {
if (p === undefined) { return p; }