(core) Add Support Grist page and nudge

Summary:
Adds a new Support Grist page (accessible only in grist-core), containing
options to opt in to telemetry and sponsor Grist Labs on GitHub.

A nudge is also shown in the doc menu, which can be collapsed or permanently
dismissed.

Test Plan: Browser and server tests.

Reviewers: paulfitz, dsagal

Reviewed By: paulfitz

Subscribers: jarek, dsagal

Differential Revision: https://phab.getgrist.com/D3926
This commit is contained in:
George Gevoian
2023-07-04 17:21:34 -04:00
parent 051c6d52fe
commit 35237a5835
47 changed files with 1743 additions and 365 deletions

View File

@@ -5,6 +5,7 @@ import {encodeUrl, getSlugIfNeeded, GristDeploymentType, GristDeploymentTypes,
GristLoadConfig, IGristUrlState, isOrgInPathOnly, parseSubdomain,
sanitizePathTail} from 'app/common/gristUrls';
import {getOrgUrlInfo} from 'app/common/gristUrls';
import {InstallProperties} from 'app/common/InstallAPI';
import {UserProfile} from 'app/common/LoginSessionAPI';
import {tbind} from 'app/common/tbind';
import * as version from 'app/common/version';
@@ -50,14 +51,15 @@ import {getAppPathTo, getAppRoot, getUnpackedAppRoot} from 'app/server/lib/place
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, optStringParam,
RequestWithGristInfo, stringParam, TEST_HTTPS_OFFSET, trustOrigin} from 'app/server/lib/requestUtils';
import {adaptServerUrl, getOrgUrl, getOriginUrl, getScope, isDefaultUser, optStringParam,
RequestWithGristInfo, sendOkReply, 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';
import {Sessions} from 'app/server/lib/Sessions';
import * as shutdown from 'app/server/lib/shutdown';
import {TagChecker} from 'app/server/lib/TagChecker';
import {ITelemetry} from 'app/server/lib/Telemetry';
import {getTelemetryPrefs, ITelemetry} from 'app/server/lib/Telemetry';
import {startTestingHooks} from 'app/server/lib/TestingHooks';
import {getTestLoginSystem} from 'app/server/lib/TestLogin';
import {addUploadRoute} from 'app/server/lib/uploads';
@@ -706,11 +708,17 @@ export class FlexServer implements GristServer {
});
}
public addTelemetry() {
public async addTelemetry() {
if (this._check('telemetry', 'homedb', 'json', 'api-mw')) { return; }
this._telemetry = this.create.Telemetry(this._dbManager, this);
this._telemetry.addEndpoints(this.app);
this._telemetry.addPages(this.app, [
this._redirectToHostMiddleware,
this._userIdMiddleware,
this._redirectToLoginWithoutExceptionsMiddleware,
]);
await this._telemetry.start();
// Start up a monitor for memory and cpu usage.
this._processMonitorStop = ProcessMonitor.start(this._telemetry);
@@ -1198,7 +1206,7 @@ export class FlexServer implements GristServer {
];
this.app.get('/account', ...middleware, expressWrap(async (req, resp) => {
return this._sendAppPage(req, resp, {path: 'account.html', status: 200, config: {}});
return this._sendAppPage(req, resp, {path: 'app.html', status: 200, config: {}});
}));
}
@@ -1460,6 +1468,43 @@ export class FlexServer implements GristServer {
addGoogleAuthEndpoint(this.app, messagePage);
}
public addInstallEndpoints() {
if (this._check('install')) { return; }
const isManager = expressWrap(
(req: express.Request, _res: express.Response, next: express.NextFunction) => {
if (!isDefaultUser(req)) { throw new ApiError('Access denied', 403); }
next();
}
);
this.app.get('/api/install/prefs', expressWrap(async (_req, resp) => {
const activation = await this._activations.current();
return sendOkReply(null, resp, {
telemetry: await getTelemetryPrefs(this._dbManager, activation),
});
}));
this.app.patch('/api/install/prefs', isManager, expressWrap(async (req, resp) => {
const props = {prefs: req.body};
const activation = await this._activations.current();
activation.checkProperties(props);
activation.updateFromProperties(props);
await activation.save();
if ((props as Partial<InstallProperties>).prefs?.telemetry) {
// Make sure the Telemetry singleton picks up the changes to telemetry preferences.
// TODO: if there are multiple home server instances, notify them all of changes to
// preferences (via Redis Pub/Sub).
await this._telemetry.fetchTelemetryPrefs();
}
return resp.status(200).send();
}));
}
// Get the HTML template sent for document pages.
public async getDocTemplate(): Promise<DocTemplate> {
const page = await fse.readFile(path.join(getAppPathTo(this.appRoot, 'static'),