mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(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:
@@ -1,3 +1,7 @@
|
||||
import {InstallPrefs} from "app/common/Install";
|
||||
import {ApiError} from "app/common/ApiError";
|
||||
import {InstallProperties, installPropertyKeys} from "app/common/InstallAPI";
|
||||
import {nativeValues} from "app/gen-server/lib/values";
|
||||
import {BaseEntity, Column, Entity, PrimaryColumn} from "typeorm";
|
||||
|
||||
@Entity({name: 'activations'})
|
||||
@@ -9,9 +13,47 @@ export class Activation extends BaseEntity {
|
||||
@Column({name: 'key', type: 'text', nullable: true})
|
||||
public key: string|null;
|
||||
|
||||
@Column({type: nativeValues.jsonEntityType, nullable: true})
|
||||
public prefs: InstallPrefs|null;
|
||||
|
||||
@Column({name: 'created_at', default: () => "CURRENT_TIMESTAMP"})
|
||||
public createdAt: Date;
|
||||
|
||||
@Column({name: 'updated_at', default: () => "CURRENT_TIMESTAMP"})
|
||||
public updatedAt: Date;
|
||||
|
||||
public checkProperties(props: any): props is Partial<InstallProperties> {
|
||||
for (const key of Object.keys(props)) {
|
||||
if (!installPropertyKeys.includes(key)) {
|
||||
throw new ApiError(`Unrecognized property ${key}`, 400);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public updateFromProperties(props: Partial<InstallProperties>) {
|
||||
if (props.prefs === undefined) { return; }
|
||||
|
||||
if (props.prefs === null) {
|
||||
this.prefs = null;
|
||||
} else {
|
||||
this.prefs = this.prefs || {};
|
||||
if (props.prefs.telemetry !== undefined) {
|
||||
this.prefs.telemetry = this.prefs.telemetry || {};
|
||||
if (props.prefs.telemetry.telemetryLevel !== undefined) {
|
||||
this.prefs.telemetry.telemetryLevel = props.prefs.telemetry.telemetryLevel;
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of Object.keys(this.prefs) as Array<keyof InstallPrefs>) {
|
||||
if (this.prefs[key] === null) {
|
||||
delete this.prefs[key];
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(this.prefs).length === 0) {
|
||||
this.prefs = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import {ApiError} from 'app/common/ApiError';
|
||||
import {DocumentUsage} from 'app/common/DocUsage';
|
||||
import {hashId} from 'app/common/hashingUtils';
|
||||
import {Role} from 'app/common/roles';
|
||||
import {DocumentOptions, DocumentProperties, documentPropertyKeys,
|
||||
DocumentType, NEW_DOCUMENT_CODE, TutorialMetadata} from "app/common/UserAPI";
|
||||
import {HomeDBManager} from 'app/gen-server/lib/HomeDBManager';
|
||||
import {DocumentOptions, DocumentProperties, documentPropertyKeys, DocumentType,
|
||||
NEW_DOCUMENT_CODE} from "app/common/UserAPI";
|
||||
import {nativeValues} from 'app/gen-server/lib/values';
|
||||
import {Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryColumn} from "typeorm";
|
||||
import {AclRuleDoc} from "./AclRule";
|
||||
@@ -92,7 +90,7 @@ export class Document extends Resource {
|
||||
return super.checkProperties(props, documentPropertyKeys);
|
||||
}
|
||||
|
||||
public updateFromProperties(props: Partial<DocumentProperties>, dbManager?: HomeDBManager) {
|
||||
public updateFromProperties(props: Partial<DocumentProperties>) {
|
||||
super.updateFromProperties(props);
|
||||
if (props.isPinned !== undefined) { this.isPinned = props.isPinned; }
|
||||
if (props.urlId !== undefined) {
|
||||
@@ -135,9 +133,6 @@ export class Document extends Resource {
|
||||
}
|
||||
if (props.options.tutorial.lastSlideIndex !== undefined) {
|
||||
this.options.tutorial.lastSlideIndex = props.options.tutorial.lastSlideIndex;
|
||||
if (dbManager && this.options.tutorial.numSlides) {
|
||||
this._emitTutorialProgressChangeEvent(dbManager, this.options.tutorial);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,26 +149,6 @@ export class Document extends Resource {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _emitTutorialProgressChangeEvent(
|
||||
dbManager: HomeDBManager,
|
||||
tutorialMetadata: TutorialMetadata
|
||||
) {
|
||||
const lastSlideIndex = tutorialMetadata.lastSlideIndex;
|
||||
const numSlides = tutorialMetadata.numSlides;
|
||||
const percentComplete = lastSlideIndex !== undefined && numSlides !== undefined
|
||||
? Math.floor((lastSlideIndex / numSlides) * 100)
|
||||
: undefined;
|
||||
dbManager?.emit('tutorialProgressChanged', {
|
||||
full: {
|
||||
tutorialForkIdDigest: hashId(this.id),
|
||||
tutorialTrunkIdDigest: this.trunkId ? hashId(this.trunkId) : undefined,
|
||||
lastSlideIndex,
|
||||
numSlides,
|
||||
percentComplete,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Check that icon points to an expected location. This will definitely
|
||||
|
||||
@@ -20,6 +20,7 @@ export class Activations {
|
||||
if (!activation) {
|
||||
activation = manager.create(Activation);
|
||||
activation.id = makeId();
|
||||
activation.prefs = {};
|
||||
await activation.save();
|
||||
}
|
||||
return activation;
|
||||
|
||||
@@ -89,14 +89,6 @@ export const NotifierEvents = StringUnion(
|
||||
|
||||
export type NotifierEvent = typeof NotifierEvents.type;
|
||||
|
||||
export const HomeDBTelemetryEvents = StringUnion(
|
||||
'tutorialProgressChanged',
|
||||
);
|
||||
|
||||
export type HomeDBTelemetryEvent = typeof HomeDBTelemetryEvents.type;
|
||||
|
||||
export type Event = NotifierEvent | HomeDBTelemetryEvent;
|
||||
|
||||
// Nominal email address of a user who can view anything (for thumbnails).
|
||||
export const PREVIEWER_EMAIL = 'thumbnail@getgrist.com';
|
||||
|
||||
@@ -324,7 +316,7 @@ export class HomeDBManager extends EventEmitter {
|
||||
orgOnly: true
|
||||
}];
|
||||
|
||||
public emit(event: Event, ...args: any[]): boolean {
|
||||
public emit(event: NotifierEvent, ...args: any[]): boolean {
|
||||
return super.emit(event, ...args);
|
||||
}
|
||||
|
||||
@@ -1960,7 +1952,7 @@ export class HomeDBManager extends EventEmitter {
|
||||
// Update the name and save.
|
||||
const doc: Document = queryResult.data;
|
||||
doc.checkProperties(props);
|
||||
doc.updateFromProperties(props, this);
|
||||
doc.updateFromProperties(props);
|
||||
if (forkId) {
|
||||
await manager.save(doc);
|
||||
return {status: 200};
|
||||
|
||||
16
app/gen-server/migration/1682636695021-ActivationPrefs.ts
Normal file
16
app/gen-server/migration/1682636695021-ActivationPrefs.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import {nativeValues} from 'app/gen-server/lib/values';
|
||||
import {MigrationInterface, QueryRunner, TableColumn} from 'typeorm';
|
||||
|
||||
export class ActivationPrefs1682636695021 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.addColumn('activations', new TableColumn({
|
||||
name: 'prefs',
|
||||
type: nativeValues.jsonType,
|
||||
isNullable: true,
|
||||
}));
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropColumn('activations', 'prefs');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user