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,4 +1,4 @@
|
||||
import {buildTelemetryEventChecker, filterMetadata, TelemetryEvent} from 'app/common/Telemetry';
|
||||
import {buildTelemetryEventChecker, TelemetryEvent} from 'app/common/Telemetry';
|
||||
import {assert} from 'chai';
|
||||
|
||||
describe('Telemetry', function() {
|
||||
@@ -132,84 +132,4 @@ describe('Telemetry', function() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterMetadata', function() {
|
||||
it('returns filtered and flattened metadata when maxLevel is "full"', function() {
|
||||
const metadata = {
|
||||
limited: {
|
||||
foo: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar: '123',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(filterMetadata(metadata, 'full'), {
|
||||
foo: 'abc',
|
||||
bar: '123',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns filtered and flattened metadata when maxLevel is "limited"', function() {
|
||||
const metadata = {
|
||||
limited: {
|
||||
foo: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar: '123',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(filterMetadata(metadata, 'limited'), {
|
||||
foo: 'abc',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns undefined when maxLevel is "off"', function() {
|
||||
assert.isUndefined(filterMetadata(undefined, 'off'));
|
||||
});
|
||||
|
||||
it('returns an empty object when metadata is empty', function() {
|
||||
assert.isEmpty(filterMetadata({}, 'full'));
|
||||
});
|
||||
|
||||
it('returns undefined when metadata is undefined', function() {
|
||||
assert.isUndefined(filterMetadata(undefined, 'full'));
|
||||
});
|
||||
|
||||
it('does not mutate metadata', function() {
|
||||
const metadata = {
|
||||
limited: {
|
||||
foo: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar: '123',
|
||||
},
|
||||
};
|
||||
filterMetadata(metadata, 'limited');
|
||||
assert.deepEqual(metadata, {
|
||||
limited: {
|
||||
foo: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar: '123',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('excludes keys with nullish values', function() {
|
||||
const metadata = {
|
||||
limited: {
|
||||
foo1: null,
|
||||
foo2: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar1: undefined,
|
||||
bar2: '123',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(filterMetadata(metadata, 'full'), {
|
||||
foo2: 'abc',
|
||||
bar2: '123',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
308
test/nbrowser/SupportGrist.ts
Normal file
308
test/nbrowser/SupportGrist.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
import {GristLoadConfig} from 'app/common/gristUrls';
|
||||
import {TelemetryLevel} from 'app/common/Telemetry';
|
||||
import {assert, driver} from 'mocha-webdriver';
|
||||
import * as gu from 'test/nbrowser/gristUtils';
|
||||
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
|
||||
import * as testUtils from 'test/server/testUtils';
|
||||
|
||||
describe('SupportGrist', function() {
|
||||
this.timeout(30000);
|
||||
setupTestSuite();
|
||||
|
||||
let oldEnv: testUtils.EnvironmentSnapshot;
|
||||
let session: gu.Session;
|
||||
|
||||
afterEach(() => gu.checkForErrors());
|
||||
|
||||
describe('in grist-core', function() {
|
||||
before(async function() {
|
||||
oldEnv = new testUtils.EnvironmentSnapshot();
|
||||
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = 'core';
|
||||
process.env.GRIST_DEFAULT_EMAIL = gu.session().email;
|
||||
await server.restart();
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
oldEnv.restore();
|
||||
await server.restart();
|
||||
});
|
||||
|
||||
describe('when user is not a manager', function() {
|
||||
before(async function() {
|
||||
oldEnv = new testUtils.EnvironmentSnapshot();
|
||||
await server.restart();
|
||||
session = await gu.session().user('user2').personalSite.login();
|
||||
await session.loadDocMenu('/');
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
oldEnv.restore();
|
||||
});
|
||||
|
||||
it('does not show a nudge on the doc menu', async function() {
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(false);
|
||||
});
|
||||
|
||||
it('shows a link to the Support Grist page in the user menu', async function() {
|
||||
await gu.openAccountMenu();
|
||||
await driver.find('.test-usermenu-support-grist').click();
|
||||
assert.isTrue(await driver.findContentWait(
|
||||
'.test-support-grist-page-sponsorship-section',
|
||||
/Sponsor Grist Labs on GitHub/,
|
||||
4000
|
||||
).isDisplayed());
|
||||
});
|
||||
|
||||
it('shows a message that telemetry is managed by the site administrator', async function() {
|
||||
assert.isTrue(await driver.findContentWait(
|
||||
'.test-support-grist-page-telemetry-section',
|
||||
/This instance is opted out of telemetry\. Only the site administrator has permission to change this\./,
|
||||
4000
|
||||
).isDisplayed());
|
||||
|
||||
process.env.GRIST_TELEMETRY_LEVEL = 'limited';
|
||||
await server.restart();
|
||||
await driver.navigate().refresh();
|
||||
assert.isTrue(await driver.findContentWait(
|
||||
'.test-support-grist-page-telemetry-section',
|
||||
/This instance is opted in to telemetry\. Only the site administrator has permission to change this\./,
|
||||
4000
|
||||
).isDisplayed());
|
||||
});
|
||||
});
|
||||
|
||||
describe('when user is a manager', function() {
|
||||
before(async function() {
|
||||
oldEnv = new testUtils.EnvironmentSnapshot();
|
||||
await server.restart();
|
||||
session = await gu.session().personalSite.login();
|
||||
await session.loadDocMenu('/');
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
oldEnv.restore();
|
||||
});
|
||||
|
||||
it('shows a nudge on the doc menu', async function() {
|
||||
// Check that the nudge is expanded by default.
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(true);
|
||||
|
||||
// Reload the doc menu and check that it's still expanded.
|
||||
await session.loadDocMenu('/');
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(true);
|
||||
|
||||
// Close the nudge and check that it's now collapsed.
|
||||
await driver.find('.test-support-grist-nudge-card-close').click();
|
||||
await assertNudgeButtonShown(true);
|
||||
await assertNudgeCardShown(false);
|
||||
|
||||
// Reload again, and check that it's still collapsed.
|
||||
await session.loadDocMenu('/');
|
||||
await assertNudgeButtonShown(true);
|
||||
await assertNudgeCardShown(false);
|
||||
|
||||
// Dismiss the contribute button and check that it's now gone, even after reloading.
|
||||
await driver.find('.test-support-grist-nudge-contribute-button').mouseMove();
|
||||
await driver.find('.test-support-grist-nudge-contribute-button-close').click();
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(false);
|
||||
await session.loadDocMenu('/');
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(false);
|
||||
});
|
||||
|
||||
it('shows a link to the Support Grist page in the user menu', async function() {
|
||||
await gu.openAccountMenu();
|
||||
await driver.find('.test-usermenu-support-grist').click();
|
||||
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000);
|
||||
assert.isFalse(await driver.find('.test-support-grist-page-telemetry-section-message').isPresent());
|
||||
});
|
||||
|
||||
it('supports opting in to telemetry from the page', async function() {
|
||||
await assertTelemetryLevel('off');
|
||||
await driver.findContentWait(
|
||||
'.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000).click();
|
||||
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt out of Telemetry/, 2000);
|
||||
assert.equal(
|
||||
await driver.find('.test-support-grist-page-telemetry-section-message').getText(),
|
||||
'You have opted in to telemetry. Thank you! 🙏'
|
||||
);
|
||||
|
||||
// Reload the page and check that the Grist config indicates telemetry is set to "limited".
|
||||
await driver.navigate().refresh();
|
||||
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt out of Telemetry/, 2000);
|
||||
assert.equal(
|
||||
await driver.findWait('.test-support-grist-page-telemetry-section-message', 2000).getText(),
|
||||
'You have opted in to telemetry. Thank you! 🙏'
|
||||
);
|
||||
await assertTelemetryLevel('limited');
|
||||
});
|
||||
|
||||
it('supports opting out of telemetry from the page', async function() {
|
||||
await driver.findContent('.test-support-grist-page-telemetry-section button', /Opt out of Telemetry/).click();
|
||||
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000);
|
||||
assert.isFalse(await driver.find('.test-support-grist-page-telemetry-section-message').isPresent());
|
||||
|
||||
// Reload the page and check that the Grist config indicates telemetry is set to "off".
|
||||
await driver.navigate().refresh();
|
||||
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000);
|
||||
assert.isFalse(await driver.find('.test-support-grist-page-telemetry-section-message').isPresent());
|
||||
await assertTelemetryLevel('off');
|
||||
});
|
||||
|
||||
it('supports opting in to telemetry from the nudge', async function() {
|
||||
// Reset all dismissed popups, including the telemetry nudge.
|
||||
await driver.executeScript('resetDismissedPopups();');
|
||||
await gu.waitForServer();
|
||||
await session.loadDocMenu('/');
|
||||
|
||||
// Opt in to telemetry and reload the page.
|
||||
await driver.find('.test-support-grist-nudge-card-opt-in').click();
|
||||
await driver.findWait('.test-support-grist-nudge-card-close-button', 1000).click();
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(false);
|
||||
await session.loadDocMenu('/');
|
||||
|
||||
// Check that the nudge is no longer shown and telemetry is set to "limited".
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(false);
|
||||
await assertTelemetryLevel('limited');
|
||||
});
|
||||
|
||||
it('does not show the nudge if telemetry is enabled', async function() {
|
||||
// Reset all dismissed popups, including the telemetry nudge.
|
||||
await driver.executeScript('resetDismissedPopups();');
|
||||
await gu.waitForServer();
|
||||
|
||||
// Reload the doc menu and check that the nudge still isn't shown.
|
||||
await session.loadDocMenu('/');
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(false);
|
||||
|
||||
// Disable telemetry from the Support Grist page.
|
||||
await gu.openAccountMenu();
|
||||
await driver.find('.test-usermenu-support-grist').click();
|
||||
await driver.findContentWait(
|
||||
'.test-support-grist-page-telemetry-section button', /Opt out of Telemetry/, 2000).click();
|
||||
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000);
|
||||
|
||||
// Reload the doc menu and check that the nudge is now shown.
|
||||
await gu.loadDocMenu('/');
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(true);
|
||||
});
|
||||
|
||||
it('shows telemetry opt-in status even when set via environment variable', async function() {
|
||||
// Set the telemetry level to "limited" via environment variable and restart the server.
|
||||
process.env.GRIST_TELEMETRY_LEVEL = 'limited';
|
||||
await server.restart();
|
||||
|
||||
// Check that the Support Grist page reports telemetry is enabled.
|
||||
await gu.loadDocMenu('/');
|
||||
await gu.openAccountMenu();
|
||||
await driver.find('.test-usermenu-support-grist').click();
|
||||
assert.equal(
|
||||
await driver.findWait('.test-support-grist-page-telemetry-section-message', 2000).getText(),
|
||||
'You have opted in to telemetry. Thank you! 🙏'
|
||||
);
|
||||
assert.isFalse(await driver.findContent('.test-support-grist-page-telemetry-section button',
|
||||
/Opt out of Telemetry/).isPresent());
|
||||
|
||||
// Now set the telemetry level to "off" and restart the server.
|
||||
process.env.GRIST_TELEMETRY_LEVEL = 'off';
|
||||
await server.restart();
|
||||
|
||||
// Check that the Support Grist page reports telemetry is disabled.
|
||||
await gu.loadDocMenu('/');
|
||||
await gu.openAccountMenu();
|
||||
await driver.find('.test-usermenu-support-grist').click();
|
||||
assert.equal(
|
||||
await driver.findWait('.test-support-grist-page-telemetry-section-message', 2000).getText(),
|
||||
'You have opted out of telemetry.'
|
||||
);
|
||||
assert.isFalse(await driver.findContent('.test-support-grist-page-telemetry-section button',
|
||||
/Opt in to Telemetry/).isPresent());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('in grist-saas', function() {
|
||||
before(async function() {
|
||||
oldEnv = new testUtils.EnvironmentSnapshot();
|
||||
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = 'saas';
|
||||
process.env.GRIST_DEFAULT_EMAIL = gu.session().email;
|
||||
await server.restart();
|
||||
session = await gu.session().personalSite.login();
|
||||
await session.loadDocMenu('/');
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
oldEnv.restore();
|
||||
await server.restart();
|
||||
});
|
||||
|
||||
it('does not show a nudge on the doc menu', async function() {
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(false);
|
||||
});
|
||||
|
||||
it('does not show a link to the Support Grist page in the user menu', async function() {
|
||||
await gu.openAccountMenu();
|
||||
assert.isFalse(await driver.find('.test-usermenu-support-grist').isPresent());
|
||||
});
|
||||
});
|
||||
|
||||
describe('in grist-enterprise', function() {
|
||||
before(async function() {
|
||||
oldEnv = new testUtils.EnvironmentSnapshot();
|
||||
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = 'enterprise';
|
||||
process.env.GRIST_DEFAULT_EMAIL = gu.session().email;
|
||||
await server.restart();
|
||||
session = await gu.session().personalSite.login();
|
||||
await session.loadDocMenu('/');
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
oldEnv.restore();
|
||||
await server.restart();
|
||||
});
|
||||
|
||||
it('does not show a nudge on the doc menu', async function() {
|
||||
await assertNudgeButtonShown(false);
|
||||
await assertNudgeCardShown(false);
|
||||
});
|
||||
|
||||
it('does not show a link to the Support Grist page in the user menu', async function() {
|
||||
await gu.openAccountMenu();
|
||||
assert.isFalse(await driver.find('.test-usermenu-support-grist').isPresent());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
async function assertNudgeButtonShown(isShown: boolean) {
|
||||
if (isShown) {
|
||||
assert.isTrue(
|
||||
await driver.find('.test-support-grist-nudge-contribute-button').isDisplayed()
|
||||
);
|
||||
} else {
|
||||
assert.isFalse(await driver.find('.test-support-grist-nudge-contribute-button').isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
async function assertNudgeCardShown(isShown: boolean) {
|
||||
if (isShown) {
|
||||
assert.isTrue(
|
||||
await driver.find('.test-support-grist-nudge-card').isDisplayed()
|
||||
);
|
||||
} else {
|
||||
assert.isFalse(await driver.find('.test-support-grist-nudge-card').isPresent());
|
||||
}
|
||||
}
|
||||
|
||||
async function assertTelemetryLevel(level: TelemetryLevel) {
|
||||
const {telemetry}: GristLoadConfig = await driver.executeScript('return window.gristConfig');
|
||||
assert.equal(telemetry?.telemetryLevel, level);
|
||||
}
|
||||
@@ -34,7 +34,7 @@ async function activateServer(home: FlexServer, docManager: DocManager) {
|
||||
home.addJsonSupport();
|
||||
await home.addLandingPages();
|
||||
home.addHomeApi();
|
||||
home.addTelemetry();
|
||||
await home.addTelemetry();
|
||||
await home.addDoc();
|
||||
home.addApiErrorHandlers();
|
||||
serverUrl = home.getOwnUrl();
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import {GristDeploymentType} from 'app/common/gristUrls';
|
||||
import {PrefSource} from 'app/common/InstallAPI';
|
||||
import {TelemetryEvent, TelemetryLevel} from 'app/common/Telemetry';
|
||||
import {ILogMeta, LogMethods} from 'app/server/lib/LogMethods';
|
||||
import {ITelemetry, Telemetry} from 'app/server/lib/Telemetry';
|
||||
import {filterMetadata, ITelemetry, Telemetry} from 'app/server/lib/Telemetry';
|
||||
import axios from 'axios';
|
||||
import {assert} from 'chai';
|
||||
import * as sinon from 'sinon';
|
||||
@@ -9,36 +10,50 @@ import {TestServer} from 'test/gen-server/apiUtils';
|
||||
import {configForUser} from 'test/gen-server/testUtils';
|
||||
|
||||
const chimpy = configForUser('Chimpy');
|
||||
const kiwi = configForUser('Kiwi');
|
||||
const anon = configForUser('Anonymous');
|
||||
|
||||
describe('Telemetry', function() {
|
||||
const deploymentTypesAndTelemetryLevels: [GristDeploymentType, TelemetryLevel][] = [
|
||||
['saas', 'off'],
|
||||
['saas', 'limited'],
|
||||
['saas', 'full'],
|
||||
['core', 'off'],
|
||||
['core', 'limited'],
|
||||
['core', 'full'],
|
||||
const variants: [GristDeploymentType, TelemetryLevel, PrefSource][] = [
|
||||
['saas', 'off', 'environment-variable'],
|
||||
['saas', 'limited', 'environment-variable'],
|
||||
['saas', 'full', 'environment-variable'],
|
||||
['core', 'off', 'environment-variable'],
|
||||
['core', 'limited', 'environment-variable'],
|
||||
['core', 'full', 'environment-variable'],
|
||||
['core', 'off', 'preferences'],
|
||||
['core', 'limited', 'preferences'],
|
||||
['core', 'full', 'preferences'],
|
||||
];
|
||||
|
||||
for (const [deploymentType, telemetryLevel] of deploymentTypesAndTelemetryLevels) {
|
||||
describe(`in grist-${deploymentType} with a telemetry level of "${telemetryLevel}"`, function() {
|
||||
for (const [deploymentType, telemetryLevel, settingSource] of variants) {
|
||||
describe(`in grist-${deploymentType} with level "${telemetryLevel}" set via ${settingSource}`, function() {
|
||||
let server: TestServer;
|
||||
let homeUrl: string;
|
||||
let installationId: string;
|
||||
let server: TestServer;
|
||||
let telemetry: ITelemetry;
|
||||
let forwardEventSpy: sinon.SinonSpy;
|
||||
let postJsonPayloadStub: sinon.SinonStub;
|
||||
let doForwardEventStub: sinon.SinonStub;
|
||||
|
||||
const sandbox = sinon.createSandbox();
|
||||
const loggedEvents: [TelemetryEvent, ILogMeta][] = [];
|
||||
|
||||
before(async function() {
|
||||
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = deploymentType;
|
||||
process.env.GRIST_TELEMETRY_LEVEL = telemetryLevel;
|
||||
if (settingSource === 'environment-variable') {
|
||||
process.env.GRIST_TELEMETRY_LEVEL = telemetryLevel;
|
||||
}
|
||||
process.env.GRIST_DEFAULT_EMAIL = 'chimpy@getgrist.com';
|
||||
server = new TestServer(this);
|
||||
homeUrl = await server.start();
|
||||
if (settingSource ==='preferences') {
|
||||
await axios.patch(`${homeUrl}/api/install/prefs`, {
|
||||
telemetry: {telemetryLevel},
|
||||
}, chimpy);
|
||||
}
|
||||
installationId = (await server.server.getActivations().current()).id;
|
||||
telemetry = server.server.getTelemetry();
|
||||
|
||||
sandbox
|
||||
.stub(LogMethods.prototype, 'rawLog')
|
||||
.callsFake((_level: string, _info: unknown, name: string, meta: ILogMeta) => {
|
||||
@@ -46,22 +61,39 @@ describe('Telemetry', function() {
|
||||
});
|
||||
forwardEventSpy = sandbox
|
||||
.spy(Telemetry.prototype as any, '_forwardEvent');
|
||||
postJsonPayloadStub = sandbox
|
||||
.stub(Telemetry.prototype as any, '_postJsonPayload');
|
||||
telemetry = server.server.getTelemetry();
|
||||
doForwardEventStub = sandbox
|
||||
.stub(Telemetry.prototype as any, '_doForwardEvent');
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
await server.stop();
|
||||
sandbox.restore();
|
||||
delete process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE;
|
||||
delete process.env.GRIST_TELEMETRY_LEVEL;
|
||||
delete process.env.GRIST_DEFAULT_EMAIL;
|
||||
await server.stop();
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('returns the current telemetry level', async function() {
|
||||
assert.equal(telemetry.getTelemetryLevel(), telemetryLevel);
|
||||
it('returns the current telemetry config', async function() {
|
||||
assert.deepEqual(telemetry.getTelemetryConfig(), {
|
||||
telemetryLevel,
|
||||
});
|
||||
});
|
||||
|
||||
if (deploymentType === 'core') {
|
||||
it('returns the current telemetry status', async function() {
|
||||
const resp = await axios.get(`${homeUrl}/api/install/prefs`, chimpy);
|
||||
assert.equal(resp.status, 200);
|
||||
assert.deepEqual(resp.data, {
|
||||
telemetry: {
|
||||
telemetryLevel: {
|
||||
value: telemetryLevel,
|
||||
source: settingSource,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (telemetryLevel !== 'off') {
|
||||
if (deploymentType === 'saas') {
|
||||
it('logs telemetry events', async function() {
|
||||
@@ -77,7 +109,7 @@ describe('Telemetry', function() {
|
||||
{
|
||||
eventName: 'documentOpened',
|
||||
eventSource: `grist-${deploymentType}`,
|
||||
docIdDigest: 'digest',
|
||||
docIdDigest: 'dige:Vq9L3nCkeufQ8euzDkXtM2Fl1cnsALqakjEeM6QlbXQ=',
|
||||
isPublic: false,
|
||||
}
|
||||
]);
|
||||
@@ -98,7 +130,7 @@ describe('Telemetry', function() {
|
||||
{
|
||||
eventName: 'documentOpened',
|
||||
eventSource: `grist-${deploymentType}`,
|
||||
docIdDigest: 'digest',
|
||||
docIdDigest: 'dige:Vq9L3nCkeufQ8euzDkXtM2Fl1cnsALqakjEeM6QlbXQ=',
|
||||
isPublic: false,
|
||||
userId: 1,
|
||||
}
|
||||
@@ -120,10 +152,11 @@ describe('Telemetry', function() {
|
||||
assert.deepEqual(forwardEventSpy.lastCall.args, [
|
||||
'documentOpened',
|
||||
{
|
||||
docIdDigest: 'digest',
|
||||
docIdDigest: 'dige:Vq9L3nCkeufQ8euzDkXtM2Fl1cnsALqakjEeM6QlbXQ=',
|
||||
isPublic: false,
|
||||
}
|
||||
]);
|
||||
assert.equal(forwardEventSpy.callCount, 1);
|
||||
}
|
||||
|
||||
if (telemetryLevel === 'full') {
|
||||
@@ -139,14 +172,15 @@ describe('Telemetry', function() {
|
||||
assert.deepEqual(forwardEventSpy.lastCall.args, [
|
||||
'documentOpened',
|
||||
{
|
||||
docIdDigest: 'digest',
|
||||
docIdDigest: 'dige:Vq9L3nCkeufQ8euzDkXtM2Fl1cnsALqakjEeM6QlbXQ=',
|
||||
isPublic: false,
|
||||
userId: 1,
|
||||
}
|
||||
]);
|
||||
// An earlier test triggered an apiUsage event.
|
||||
assert.equal(forwardEventSpy.callCount, 2);
|
||||
}
|
||||
|
||||
assert.equal(forwardEventSpy.callCount, 1);
|
||||
assert.isEmpty(loggedEvents);
|
||||
});
|
||||
}
|
||||
@@ -179,13 +213,6 @@ describe('Telemetry', function() {
|
||||
});
|
||||
|
||||
if (telemetryLevel === 'limited') {
|
||||
it('throws an error when an event requires an elevated telemetry level', async function() {
|
||||
await assert.isRejected(
|
||||
telemetry.logEvent('signupVerified', {}),
|
||||
/Telemetry event signupVerified requires a minimum telemetry level of 2 but the current level is 1/
|
||||
);
|
||||
});
|
||||
|
||||
it("throws an error when an event's metadata requires an elevated telemetry level", async function() {
|
||||
await assert.isRejected(
|
||||
telemetry.logEvent('documentOpened', {limited: {userId: 1}}),
|
||||
@@ -290,16 +317,16 @@ describe('Telemetry', function() {
|
||||
if (telemetryLevel === 'limited') {
|
||||
assert.equal(forwardEventSpy.callCount, 2);
|
||||
} else {
|
||||
// The POST above also triggers an "apiUsage" event.
|
||||
assert.equal(forwardEventSpy.callCount, 3);
|
||||
assert.equal(forwardEventSpy.secondCall.args[0], 'apiUsage');
|
||||
// The count below includes 2 apiUsage events triggered as side effects.
|
||||
assert.equal(forwardEventSpy.callCount, 4);
|
||||
assert.equal(forwardEventSpy.thirdCall.args[0], 'apiUsage');
|
||||
}
|
||||
assert.isEmpty(loggedEvents);
|
||||
});
|
||||
|
||||
it('skips forwarding events if too many requests are pending', async function() {
|
||||
let numRequestsMade = 0;
|
||||
postJsonPayloadStub.callsFake(async () => {
|
||||
doForwardEventStub.callsFake(async () => {
|
||||
numRequestsMade += 1;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
});
|
||||
@@ -329,4 +356,169 @@ describe('Telemetry', function() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
describe('api', function() {
|
||||
let server: TestServer;
|
||||
let homeUrl: string;
|
||||
|
||||
const sandbox = sinon.createSandbox();
|
||||
|
||||
before(async function() {
|
||||
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = 'core';
|
||||
process.env.GRIST_DEFAULT_EMAIL = 'chimpy@getgrist.com';
|
||||
server = new TestServer(this);
|
||||
homeUrl = await server.start();
|
||||
sandbox.stub(Telemetry.prototype as any, '_doForwardEvent');
|
||||
});
|
||||
|
||||
after(async function() {
|
||||
delete process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE;
|
||||
delete process.env.GRIST_DEFAULT_EMAIL;
|
||||
await server.stop();
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it('GET /install/prefs returns 200 for non-default users', async function() {
|
||||
const resp = await axios.get(`${homeUrl}/api/install/prefs`, kiwi);
|
||||
assert.equal(resp.status, 200);
|
||||
assert.deepEqual(resp.data, {
|
||||
telemetry: {
|
||||
telemetryLevel: {
|
||||
value: 'off',
|
||||
source: 'preferences',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('GET /install/prefs returns 200 for the default user', async function() {
|
||||
const resp = await axios.get(`${homeUrl}/api/install/prefs`, chimpy);
|
||||
assert.equal(resp.status, 200);
|
||||
assert.deepEqual(resp.data, {
|
||||
telemetry: {
|
||||
telemetryLevel: {
|
||||
value: 'off',
|
||||
source: 'preferences',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('PATCH /install/prefs returns 403 for non-default users', async function() {
|
||||
const resp = await axios.patch(`${homeUrl}/api/install/prefs`, {
|
||||
telemetry: {telemetryLevel: 'limited'},
|
||||
}, kiwi);
|
||||
assert.equal(resp.status, 403);
|
||||
});
|
||||
|
||||
it('PATCH /install/prefs returns 200 for the default user', async function() {
|
||||
let resp = await axios.patch(`${homeUrl}/api/install/prefs`, {
|
||||
telemetry: {telemetryLevel: 'limited'},
|
||||
}, chimpy);
|
||||
assert.equal(resp.status, 200);
|
||||
|
||||
resp = await axios.get(`${homeUrl}/api/install/prefs`, chimpy);
|
||||
assert.deepEqual(resp.data, {
|
||||
telemetry: {
|
||||
telemetryLevel: {
|
||||
value: 'limited',
|
||||
source: 'preferences',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('filterMetadata', function() {
|
||||
it('returns filtered and flattened metadata when maxLevel is "full"', function() {
|
||||
const metadata = {
|
||||
limited: {
|
||||
foo: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar: '123',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(filterMetadata(metadata, 'full'), {
|
||||
foo: 'abc',
|
||||
bar: '123',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns filtered and flattened metadata when maxLevel is "limited"', function() {
|
||||
const metadata = {
|
||||
limited: {
|
||||
foo: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar: '123',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(filterMetadata(metadata, 'limited'), {
|
||||
foo: 'abc',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns undefined when maxLevel is "off"', function() {
|
||||
assert.isUndefined(filterMetadata(undefined, 'off'));
|
||||
});
|
||||
|
||||
it('returns an empty object when metadata is empty', function() {
|
||||
assert.isEmpty(filterMetadata({}, 'full'));
|
||||
});
|
||||
|
||||
it('returns undefined when metadata is undefined', function() {
|
||||
assert.isUndefined(filterMetadata(undefined, 'full'));
|
||||
});
|
||||
|
||||
it('does not mutate metadata', function() {
|
||||
const metadata = {
|
||||
limited: {
|
||||
foo: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar: '123',
|
||||
},
|
||||
};
|
||||
filterMetadata(metadata, 'limited');
|
||||
assert.deepEqual(metadata, {
|
||||
limited: {
|
||||
foo: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar: '123',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('excludes keys with nullish values', function() {
|
||||
const metadata = {
|
||||
limited: {
|
||||
foo1: null,
|
||||
foo2: 'abc',
|
||||
},
|
||||
full: {
|
||||
bar1: undefined,
|
||||
bar2: '123',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(filterMetadata(metadata, 'full'), {
|
||||
foo2: 'abc',
|
||||
bar2: '123',
|
||||
});
|
||||
});
|
||||
|
||||
it('hashes keys suffixed with "Digest"', function() {
|
||||
const metadata = {
|
||||
limited: {
|
||||
docIdDigest: 'FGWGX4S6TB6',
|
||||
docId: '3WH3D68J28',
|
||||
},
|
||||
};
|
||||
assert.deepEqual(filterMetadata(metadata, 'limited'), {
|
||||
docIdDigest: 'FGWG:omhYAysWiM7coZK+FLK/tIOPW4BaowXjU7J/P9ynYcU=',
|
||||
docId: '3WH3D68J28',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user