2024-03-23 17:11:06 +00:00
|
|
|
import {TelemetryLevel} from 'app/common/Telemetry';
|
|
|
|
import {assert, driver, Key, WebElement} from 'mocha-webdriver';
|
|
|
|
import * as gu from 'test/nbrowser/gristUtils';
|
|
|
|
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
|
2024-04-29 14:54:03 +00:00
|
|
|
import {Defer, serveSomething, Serving} from 'test/server/customUtil';
|
2024-03-23 17:11:06 +00:00
|
|
|
import * as testUtils from 'test/server/testUtils';
|
2024-04-29 14:54:03 +00:00
|
|
|
import express from 'express';
|
2024-03-23 17:11:06 +00:00
|
|
|
|
|
|
|
describe('AdminPanel', function() {
|
2024-04-29 14:54:03 +00:00
|
|
|
this.timeout(300000);
|
2024-03-23 17:11:06 +00:00
|
|
|
setupTestSuite();
|
|
|
|
|
|
|
|
let oldEnv: testUtils.EnvironmentSnapshot;
|
|
|
|
let session: gu.Session;
|
2024-04-29 14:54:03 +00:00
|
|
|
let fakeServer: FakeUpdateServer;
|
2024-03-23 17:11:06 +00:00
|
|
|
|
|
|
|
afterEach(() => gu.checkForErrors());
|
|
|
|
|
|
|
|
before(async function() {
|
|
|
|
oldEnv = new testUtils.EnvironmentSnapshot();
|
|
|
|
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = 'core';
|
|
|
|
process.env.GRIST_DEFAULT_EMAIL = gu.session().email;
|
2024-04-29 14:54:03 +00:00
|
|
|
fakeServer = await startFakeServer();
|
|
|
|
process.env.GRIST_TEST_VERSION_CHECK_URL = `${fakeServer.url()}/version`;
|
2024-03-23 17:11:06 +00:00
|
|
|
await server.restart(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
after(async function() {
|
2024-04-29 14:54:03 +00:00
|
|
|
await fakeServer.close();
|
2024-03-23 17:11:06 +00:00
|
|
|
oldEnv.restore();
|
|
|
|
await server.restart(true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not be shown to non-managers', async function() {
|
|
|
|
session = await gu.session().user('user2').personalSite.login();
|
|
|
|
await session.loadDocMenu('/');
|
|
|
|
|
|
|
|
await gu.openAccountMenu();
|
|
|
|
assert.equal(await driver.find('.test-usermenu-admin-panel').isPresent(), false);
|
|
|
|
await driver.sendKeys(Key.ESCAPE);
|
|
|
|
assert.equal(await driver.find('.test-dm-admin-panel').isPresent(), false);
|
|
|
|
|
|
|
|
// Try loading the URL directly.
|
|
|
|
await driver.get(`${server.getHost()}/admin`);
|
|
|
|
assert.match(await driver.findWait('.test-error-header', 2000).getText(), /Access denied/);
|
|
|
|
assert.equal(await driver.find('.test-admin-panel').isPresent(), false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should be shown to managers', async function() {
|
|
|
|
session = await gu.session().personalSite.login();
|
|
|
|
await session.loadDocMenu('/');
|
|
|
|
assert.equal(await driver.find('.test-dm-admin-panel').isDisplayed(), true);
|
|
|
|
assert.match(await driver.find('.test-dm-admin-panel').getAttribute('href'), /\/admin$/);
|
|
|
|
await gu.openAccountMenu();
|
|
|
|
assert.equal(await driver.find('.test-usermenu-admin-panel').isDisplayed(), true);
|
|
|
|
assert.match(await driver.find('.test-usermenu-admin-panel').getAttribute('href'), /\/admin$/);
|
|
|
|
await driver.find('.test-usermenu-admin-panel').click();
|
|
|
|
assert.equal(await waitForAdminPanel().isDisplayed(), true);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should include support-grist section', async function() {
|
|
|
|
assert.match(await driver.find('.test-admin-panel-item-sponsor').getText(), /Support Grist Labs on GitHub/);
|
|
|
|
await withExpandedItem('sponsor', async () => {
|
|
|
|
const button = await driver.find('.test-support-grist-page-sponsorship-section');
|
|
|
|
assert.equal(await button.isDisplayed(), true);
|
|
|
|
assert.match(await button.getText(), /You can support Grist open-source/);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('supports opting in to telemetry from the page', async function() {
|
|
|
|
await assertTelemetryLevel('off');
|
|
|
|
|
|
|
|
let toggle = driver.find('.test-admin-panel-item-value-telemetry .widget_switch');
|
|
|
|
assert.equal(await isSwitchOn(toggle), false);
|
|
|
|
|
|
|
|
await withExpandedItem('telemetry', async () => {
|
|
|
|
assert.isFalse(await driver.find('.test-support-grist-page-telemetry-section-message').isPresent());
|
|
|
|
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! 🙏'
|
|
|
|
);
|
|
|
|
assert.equal(await isSwitchOn(toggle), true);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Check it's still on after collapsing.
|
|
|
|
assert.equal(await isSwitchOn(toggle), true);
|
|
|
|
|
|
|
|
// Reload the page and check that the Grist config indicates telemetry is set to "limited".
|
|
|
|
await driver.navigate().refresh();
|
|
|
|
await waitForAdminPanel();
|
|
|
|
toggle = driver.find('.test-admin-panel-item-value-telemetry .widget_switch');
|
|
|
|
assert.equal(await isSwitchOn(toggle), true);
|
|
|
|
await toggleItem('telemetry');
|
|
|
|
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());
|
|
|
|
let toggle = driver.find('.test-admin-panel-item-value-telemetry .widget_switch');
|
|
|
|
assert.equal(await isSwitchOn(toggle), false);
|
|
|
|
|
|
|
|
// Reload the page and check that the Grist config indicates telemetry is set to "off".
|
|
|
|
await driver.navigate().refresh();
|
|
|
|
await waitForAdminPanel();
|
|
|
|
await toggleItem('telemetry');
|
|
|
|
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');
|
|
|
|
toggle = driver.find('.test-admin-panel-item-value-telemetry .widget_switch');
|
|
|
|
assert.equal(await isSwitchOn(toggle), false);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('supports toggling telemetry from the toggle in the top line', async function() {
|
|
|
|
const toggle = driver.find('.test-admin-panel-item-value-telemetry .widget_switch');
|
|
|
|
assert.equal(await isSwitchOn(toggle), false);
|
|
|
|
await toggle.click();
|
|
|
|
await gu.waitForServer();
|
|
|
|
assert.equal(await isSwitchOn(toggle), true);
|
|
|
|
assert.match(await driver.find('.test-support-grist-page-telemetry-section-message').getText(),
|
|
|
|
/You have opted in/);
|
|
|
|
await toggle.click();
|
|
|
|
await gu.waitForServer();
|
|
|
|
assert.equal(await isSwitchOn(toggle), false);
|
|
|
|
await withExpandedItem('telemetry', async () => {
|
|
|
|
assert.equal(await driver.find('.test-support-grist-page-telemetry-section-message').isPresent(), false);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
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 driver.get(`${server.getHost()}/admin`);
|
|
|
|
await waitForAdminPanel();
|
|
|
|
const toggle = driver.find('.test-admin-panel-item-value-telemetry .widget_switch');
|
|
|
|
assert.equal(await isSwitchOn(toggle), true);
|
|
|
|
await toggleItem('telemetry');
|
|
|
|
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 driver.get(`${server.getHost()}/admin`);
|
|
|
|
await waitForAdminPanel();
|
|
|
|
await toggleItem('telemetry');
|
|
|
|
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());
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should show version', async function() {
|
|
|
|
await driver.get(`${server.getHost()}/admin`);
|
|
|
|
await waitForAdminPanel();
|
|
|
|
assert.equal(await driver.find('.test-admin-panel-item-version').isDisplayed(), true);
|
|
|
|
assert.match(await driver.find('.test-admin-panel-item-value-version').getText(), /^Version \d+\./);
|
|
|
|
});
|
2024-04-29 14:54:03 +00:00
|
|
|
|
2024-04-30 00:31:10 +00:00
|
|
|
it('should show sandbox', async function() {
|
|
|
|
await driver.get(`${server.getHost()}/admin`);
|
|
|
|
await waitForAdminPanel();
|
|
|
|
assert.equal(await driver.find('.test-admin-panel-item-sandboxing').isDisplayed(), true);
|
|
|
|
await gu.waitToPass(
|
2024-04-30 01:57:02 +00:00
|
|
|
// unknown for grist-saas, unconfigured for grist-core.
|
|
|
|
async () => assert.match(await driver.find('.test-admin-panel-item-value-sandboxing').getText(),
|
|
|
|
/^((unknown)|(unconfigured))/),
|
2024-04-30 00:31:10 +00:00
|
|
|
3000,
|
|
|
|
);
|
|
|
|
// It would be good to test other scenarios, but we are using
|
2024-04-30 01:57:02 +00:00
|
|
|
// a multi-server setup on grist-saas and the sandbox test isn't
|
|
|
|
// useful there yet.
|
2024-04-30 00:31:10 +00:00
|
|
|
});
|
|
|
|
|
2024-04-29 14:54:03 +00:00
|
|
|
const upperCheckNow = () => driver.find('.test-admin-panel-updates-upper-check-now');
|
|
|
|
const lowerCheckNow = () => driver.find('.test-admin-panel-updates-lower-check-now');
|
|
|
|
const autoCheckToggle = () => driver.find('.test-admin-panel-updates-auto-check');
|
|
|
|
const updateMessage = () => driver.find('.test-admin-panel-updates-message');
|
|
|
|
const versionBox = () => driver.find('.test-admin-panel-updates-version');
|
|
|
|
function waitForStatus(message: RegExp) {
|
|
|
|
return gu.waitToPass(async () => {
|
|
|
|
assert.match(await updateMessage().getText(), message);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
it('should check for updates', async function() {
|
|
|
|
// Clear any cached settings.
|
|
|
|
await driver.executeScript('window.sessionStorage.clear(); window.localStorage.clear();');
|
|
|
|
await driver.navigate().refresh();
|
|
|
|
await waitForAdminPanel();
|
|
|
|
|
|
|
|
// By default don't have any info.
|
|
|
|
await waitForStatus(/No information available/);
|
|
|
|
|
|
|
|
// We see upper check-now button.
|
|
|
|
assert.isTrue(await upperCheckNow().isDisplayed());
|
|
|
|
|
|
|
|
// We can expand.
|
|
|
|
await toggleItem('updates');
|
|
|
|
|
|
|
|
// We see a toggle to update automatically.
|
|
|
|
assert.isTrue(await autoCheckToggle().isDisplayed());
|
|
|
|
assert.isFalse(await isSwitchOn(autoCheckToggle()));
|
|
|
|
|
|
|
|
// We can click it, Grist will turn on auto checks and do it right away.
|
|
|
|
fakeServer.pause();
|
|
|
|
await autoCheckToggle().click();
|
|
|
|
assert.isTrue(await isSwitchOn(autoCheckToggle()));
|
|
|
|
|
|
|
|
// It will first show "Checking for updates" message.
|
|
|
|
// (Request is blocked by fake server, so it will not complete until we resume it.)
|
|
|
|
await waitForStatus(/Checking for updates/);
|
|
|
|
|
|
|
|
// Upper check now button is removed.
|
|
|
|
assert.isFalse(await upperCheckNow().isPresent());
|
|
|
|
|
|
|
|
// Resume server and respond.
|
|
|
|
fakeServer.resume();
|
|
|
|
|
|
|
|
// It will show "New version available" message.
|
|
|
|
await waitForStatus(/Newer version available/);
|
|
|
|
// And a version number.
|
|
|
|
assert.isTrue(await versionBox().isDisplayed());
|
|
|
|
assert.match(await versionBox().getText(), /Version 9\.9\.9/);
|
|
|
|
|
|
|
|
// When we reload, we will auto check for updates.
|
|
|
|
fakeServer.pause();
|
|
|
|
fakeServer.latestVersion = await currentVersion();
|
|
|
|
await driver.navigate().refresh();
|
|
|
|
await waitForAdminPanel();
|
|
|
|
await waitForStatus(/Checking for updates/);
|
|
|
|
fakeServer.resume();
|
|
|
|
await waitForStatus(/Grist is up to date/);
|
|
|
|
|
|
|
|
// Disable auto-checks.
|
|
|
|
await toggleItem('updates');
|
|
|
|
assert.isTrue(await isSwitchOn(autoCheckToggle()));
|
|
|
|
await autoCheckToggle().click();
|
|
|
|
assert.isFalse(await isSwitchOn(autoCheckToggle()));
|
|
|
|
// Nothing should happen.
|
|
|
|
await waitForStatus(/Grist is up to date/);
|
|
|
|
assert.isTrue(await versionBox().isDisplayed());
|
|
|
|
assert.equal(await versionBox().getText(), `Version ${await currentVersion()}`);
|
|
|
|
|
|
|
|
// Refresh to see if we are disabled.
|
|
|
|
fakeServer.pause();
|
|
|
|
await driver.navigate().refresh();
|
|
|
|
await waitForAdminPanel();
|
|
|
|
await waitForStatus(/Last checked .+ ago/);
|
|
|
|
fakeServer.resume();
|
|
|
|
// Expand and see if the toggle is off.
|
|
|
|
await toggleItem('updates');
|
|
|
|
assert.isFalse(await isSwitchOn(autoCheckToggle()));
|
|
|
|
});
|
|
|
|
|
|
|
|
it('shows up-to-date message', async function() {
|
|
|
|
fakeServer.latestVersion = await currentVersion();
|
|
|
|
// Click upper check now.
|
|
|
|
await waitForStatus(/Last checked .+ ago/);
|
|
|
|
await upperCheckNow().click();
|
|
|
|
await waitForStatus(/Grist is up to date/);
|
|
|
|
|
|
|
|
// Update version once again.
|
|
|
|
fakeServer.latestVersion = '9.9.10';
|
|
|
|
// Click lower check now.
|
|
|
|
fakeServer.pause();
|
|
|
|
await lowerCheckNow().click();
|
|
|
|
await waitForStatus(/Checking for updates/);
|
|
|
|
fakeServer.resume();
|
|
|
|
await waitForStatus(/Newer version available/);
|
|
|
|
|
|
|
|
// Make sure we see the new version.
|
|
|
|
assert.isTrue(await versionBox().isDisplayed());
|
|
|
|
assert.match(await versionBox().getText(), /Version 9\.9\.10/);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('shows error message', async function() {
|
|
|
|
fakeServer.failNext = true;
|
|
|
|
fakeServer.pause();
|
|
|
|
await lowerCheckNow().click();
|
|
|
|
await waitForStatus(/Checking for updates/);
|
|
|
|
fakeServer.resume();
|
|
|
|
await waitForStatus(/Error checking for updates/);
|
|
|
|
assert.match((await gu.getToasts())[0], /some error/);
|
|
|
|
await gu.wipeToasts();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should send telemetry data', async function() {
|
|
|
|
assert.deepEqual({...fakeServer.payload, installationId: 'test'}, {
|
|
|
|
installationId: 'test',
|
|
|
|
deploymentType: 'core',
|
|
|
|
currentVersion: await currentVersion(),
|
|
|
|
});
|
|
|
|
assert.isNotEmpty(fakeServer.payload.installationId);
|
|
|
|
});
|
2024-03-23 17:11:06 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
async function assertTelemetryLevel(level: TelemetryLevel) {
|
|
|
|
const telemetryLevel = await driver.executeScript('return window.gristConfig.telemetry?.telemetryLevel');
|
|
|
|
assert.equal(telemetryLevel, level);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function toggleItem(itemId: string) {
|
|
|
|
const header = await driver.find(`.test-admin-panel-item-name-${itemId}`);
|
|
|
|
await header.click();
|
|
|
|
await driver.sleep(500); // Time to expand or collapse.
|
|
|
|
return header;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function withExpandedItem(itemId: string, callback: () => Promise<void>) {
|
|
|
|
const header = await toggleItem(itemId);
|
|
|
|
await callback();
|
|
|
|
await header.click();
|
|
|
|
await driver.sleep(500); // Time to collapse.
|
|
|
|
}
|
|
|
|
|
|
|
|
const isSwitchOn = (switchElem: WebElement) => switchElem.matches('[class*=switch_on]');
|
|
|
|
const waitForAdminPanel = () => driver.findWait('.test-admin-panel', 2000);
|
2024-04-29 14:54:03 +00:00
|
|
|
|
|
|
|
interface FakeUpdateServer {
|
|
|
|
latestVersion: string;
|
|
|
|
failNext: boolean;
|
|
|
|
payload: any;
|
|
|
|
close: () => Promise<void>;
|
|
|
|
pause: () => void;
|
|
|
|
resume: () => void;
|
|
|
|
url: () => string;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function startFakeServer() {
|
|
|
|
let mutex: Defer|null = null;
|
|
|
|
const API: FakeUpdateServer = {
|
|
|
|
latestVersion: '9.9.9',
|
|
|
|
failNext: false,
|
|
|
|
payload: null,
|
|
|
|
close: async () => {
|
|
|
|
mutex?.resolve();
|
|
|
|
mutex = null;
|
|
|
|
await server?.shutdown();
|
|
|
|
server = null;
|
|
|
|
},
|
|
|
|
pause: () => {
|
|
|
|
mutex = new Defer();
|
|
|
|
},
|
|
|
|
resume: () => {
|
|
|
|
mutex?.resolve();
|
|
|
|
mutex = null;
|
|
|
|
},
|
|
|
|
url: () => {
|
|
|
|
return server!.url;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let server: Serving|null = await serveSomething((app) => {
|
|
|
|
app.use(express.json());
|
|
|
|
app.post('/version', async (req, res, next) => {
|
|
|
|
API.payload = req.body;
|
|
|
|
try {
|
|
|
|
await mutex;
|
|
|
|
if (API.failNext) {
|
|
|
|
res.status(500).json({error: 'some error'});
|
|
|
|
API.failNext = false;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
res.json({latestVersion: API.latestVersion});
|
|
|
|
} catch(ex) {
|
|
|
|
next(ex);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return API;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function currentVersion() {
|
|
|
|
const currentVersionText = await driver.find(".test-admin-panel-item-value-version").getText();
|
|
|
|
const currentVersion = currentVersionText.match(/Version (.+)/)![1];
|
|
|
|
return currentVersion;
|
|
|
|
}
|