From 76e822eb23b050ae75921190a6bbe2ecc0ba1ee4 Mon Sep 17 00:00:00 2001 From: George Gevoian Date: Sat, 9 Sep 2023 17:01:53 -0400 Subject: [PATCH] (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 --- app/client/ui/WelcomeQuestions.ts | 5 ++--- app/common/Telemetry.ts | 20 ++++++++++++++++++++ app/server/lib/FlexServer.ts | 20 ++++++++++++++++++-- app/server/lib/requestUtils.ts | 11 +++++++++++ 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/app/client/ui/WelcomeQuestions.ts b/app/client/ui/WelcomeQuestions.ts index 2683cd8c..fdd752a3 100644 --- a/app/client/ui/WelcomeQuestions.ts +++ b/app/client/ui/WelcomeQuestions.ts @@ -27,9 +27,8 @@ export function showWelcomeQuestions(userPrefsObs: Observable) { 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'; diff --git a/app/common/Telemetry.ts b/app/common/Telemetry.ts index dc9f3085..4c940bdc 100644 --- a/app/common/Telemetry.ts +++ b/app/common/Telemetry.ts @@ -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; @@ -891,6 +910,7 @@ export const TelemetryEvents = StringUnion( 'tutorialProgressChanged', 'tutorialRestarted', 'watchedVideoTour', + 'welcomeQuestionsSubmitted', ); export type TelemetryEvent = typeof TelemetryEvents.type; diff --git a/app/server/lib/FlexServer.ts b/app/server/lib/FlexServer.ts index 6fa23079..6f8407a8 100644 --- a/app/server/lib/FlexServer.ts +++ b/app/server/lib/FlexServer.ts @@ -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. diff --git a/app/server/lib/requestUtils.ts b/app/server/lib/requestUtils.ts index a093da8d..890b9702 100644 --- a/app/server/lib/requestUtils.ts +++ b/app/server/lib/requestUtils.ts @@ -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; }