(core) updates from grist-core

pull/598/head
Alex Hall 10 months ago
commit 9b87a6f06a

@ -244,7 +244,7 @@ export class SelectionSummary extends Disposable {
} else {
for (const i of rowIndices) {
const value = values[i];
if (value !== null && value !== undefined && value !== '' && !isEmpty?.(value)) {
if (value !== null && value !== undefined && value !== '' && value !== false && !isEmpty?.(value)) {
countNonEmpty++;
}
}

@ -77,13 +77,23 @@ export class UrlState<IUrlState extends object> extends Disposable {
if (samePage) {
await this._stateImpl.delayPushUrl(prevState, newState);
if (options.replace) {
this._window.history.replaceState(null, '', newUrl);
} else {
this._window.history.pushState(null, '', newUrl);
try {
if (options.replace) {
this._window.history.replaceState(null, '', newUrl);
} else {
this._window.history.pushState(null, '', newUrl);
}
// pushState/replaceState above do not trigger 'popstate' event, so we call loadState() manually.
this.loadState();
} catch (e) {
// If we fail, we may be in a context where Grist doesn't have
// control over history, e.g. an iframe with srcdoc. Go ahead
// and apply the application state change (e.g. switching to a
// different Grist page). The back button won't work, but what
// it should do in an embedded context is full of nuance anyway.
log.debug(`pushUrl failure: ${e}`);
this.state.set(this._stateImpl.decodeUrl(new URL(newUrl)));
}
// pushState/replaceState above do not trigger 'popstate' event, so we call loadState() manually.
this.loadState();
} else {
this._window._urlStateLoadPage!(newUrl);
}

@ -119,13 +119,13 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
public readonly currentWorkspace = Computed.create(this, this.currentDoc, (use, doc) => doc && doc.workspace);
public readonly currentOrg = Computed.create(this, this.currentWorkspace, (use, ws) => ws && ws.org);
public readonly currentOrgName = Computed.create(this, this.currentOrg,
(use, org) => getOrgNameOrGuest(org, this.appModel.currentUser));
(use, org) => getOrgNameOrGuest(org, this.appModel.currentUser));
public readonly currentDocTitle = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.name : '');
public readonly isReadonly = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isReadonly : false);
public readonly isPrefork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isPreFork : false);
public readonly isFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isFork : false);
public readonly isRecoveryMode = Computed.create(this, this.currentDoc,
(use, doc) => doc ? doc.isRecoveryMode : false);
(use, doc) => doc ? doc.isRecoveryMode : false);
public readonly userOverride = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.userOverride : null);
public readonly isBareFork = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isBareFork : false);
public readonly isSnapshot = Computed.create(this, this.currentDoc, (use, doc) => doc ? doc.isSnapshot : false);
@ -173,7 +173,7 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
FlowRunner.create(this._openerHolder,
(flow: AsyncFlow) => this._openDoc(flow, urlId, urlOpenMode, state.params?.compare, linkParameters)
)
.resultPromise.catch(err => this._onOpenError(err));
.resultPromise.catch(err => this._onOpenError(err));
}
}
}));
@ -270,7 +270,7 @@ export class DocPageModelImpl extends Disposable implements DocPageModel {
explanation: (
isDocOwner
? t("You can try reloading the document, or using recovery mode. \
Recovery mode opens the document to be fully accessible to owners, and inaccessible to others. \
Recovery mode opens the document to be fully accessible to owners, and inaccessible to others. \
It also disables formulas. [{{error}}]", {error: err.message})
: isDenied
? t('Sorry, access to this document has been denied. [{{error}}]', {error: err.message})
@ -305,7 +305,7 @@ It also disables formulas. [{{error}}]", {error: err.message})
comparisonUrlId: string | undefined,
linkParameters: Record<string, string> | undefined): Promise<void> {
console.log(`DocPageModel _openDoc starting for ${urlId} (mode ${urlOpenMode})` +
(comparisonUrlId ? ` (compare ${comparisonUrlId})` : ''));
(comparisonUrlId ? ` (compare ${comparisonUrlId})` : ''));
const gristDocModulePromise = loadGristDoc();
const docResponse = await retryOnNetworkError(flow, getDoc.bind(null, this._api, urlId));
@ -379,7 +379,7 @@ It also disables formulas. [{{error}}]", {error: err.message})
await this._api.getDocAPI(urlId).compareDoc(comparisonUrlId, { detail: true }) : undefined;
const gristDoc = gdModule.GristDoc.create(flow, this._appObj, this.appModel, docComm, this, openDocResponse,
this.appModel.topAppModel.plugins, {comparison});
this.appModel.topAppModel.plugins, {comparison});
// Move ownership of docComm to GristDoc.
gristDoc.autoDispose(flow.release(docComm));
@ -403,13 +403,13 @@ function addMenu(importSources: ImportSource[], gristDoc: GristDoc, isReadonly:
return [
menuItem(
(elem) => openPageWidgetPicker(elem, gristDoc, (val) => gristDoc.addNewPage(val).catch(reportError),
{isNewPage: true, buttonLabel: 'Add Page'}),
{isNewPage: true, buttonLabel: 'Add Page'}),
menuIcon("Page"), t("Add Page"), testId('dp-add-new-page'),
dom.cls('disabled', isReadonly)
),
menuItem(
(elem) => openPageWidgetPicker(elem, gristDoc, (val) => gristDoc.addWidgetToPage(val).catch(reportError),
{isNewPage: false, selectBy}),
{isNewPage: false, selectBy}),
menuIcon("Widget"), t("Add Widget to Page"), testId('dp-add-widget-to-page'),
// disable for readonly doc and all special views
dom.cls('disabled', (use) => typeof use(gristDoc.activeViewId) !== 'number' || isReadonly),

@ -657,8 +657,9 @@ def TASTEME(food):
@unimplemented
def TEXT(number, format_type): # pylint: disable=unused-argument
"""
Converts a number into text according to a specified format. It is not yet implemented in
Grist.
Converts a number into text according to a specified format. It is not yet implemented in
Grist. You can use the similar Python functions str() to convert numbers into strings, and
optionally format() to specify the number format.
"""
raise NotImplementedError()

@ -397,7 +397,8 @@
"GristDoc": {
"Added new linked section to view {{viewName}}": "Added new linked section to view {{viewName}}",
"Import from file": "Import from file",
"Saved linked section {{title}} in view {{name}}": "Saved linked section {{title}} in view {{name}}"
"Saved linked section {{title}} in view {{name}}": "Saved linked section {{title}} in view {{name}}",
"go to webhook settings": "go to webhook settings"
},
"HomeIntro": {
"Any documents created in this site will appear here.": "Any documents created in this site will appear here.",

Binary file not shown.

@ -391,6 +391,7 @@ describe('Dates.ntest', function() {
await gu.clickCellRC(0, 1);
await gu.sendKeys([$.ALT, '=']);
await gu.waitForServer();
await gu.waitAppFocus(false);
await gu.sendKeys("Diff", $.ENTER);
await gu.waitForServer();
await gu.sendKeys('=');

@ -4,7 +4,7 @@ import * as gu from 'test/nbrowser/gristUtils';
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
describe('SelectByRefList', function() {
this.timeout(60000);
this.timeout(80000);
setupTestSuite();
addToRepl('gu2', gu);
gu.bigScreen();

@ -130,11 +130,6 @@ describe('SelectionSummary', function () {
count: 2,
sum: null,
});
await selectAndAssert({col: 2, row: 0}, {col: 3, row: 5}, {
dimensions: '62',
count: 11,
sum: null,
});
// Scroll horizontally to the end of the table.
await gu.sendKeys(Key.END);
@ -151,6 +146,15 @@ describe('SelectionSummary', function () {
});
});
it('does not count false values', async function () {
// False values in boolean columns should not be included in count
await selectAndAssert({col: 2, row: 0}, {col: 3, row: 5}, {
dimensions: '62',
count: 9,
sum: null,
});
});
it('uses the show column of reference columns for computations', async function () {
// Column 6 is a Reference column pointing to column 0.
await gu.sendKeys(Key.HOME);

@ -16,7 +16,6 @@ describe('WebhookOverflow', function () {
before(async function () {
oldEnv = new EnvironmentSnapshot();
//host = new URL(server.getHost()).host;
process.env.ALLOWED_WEBHOOK_DOMAINS = '*';
process.env.GRIST_MAX_QUEUE_SIZE = '2';
await server.restart();
@ -50,45 +49,31 @@ describe('WebhookOverflow', function () {
await driver.sendKeys(...keys);
}
it('should show a message when overflown', async function () {
it('should show a message when overflowed', async function () {
await gu.openPage('Table2');
await gu.getCell('A', 1).click();
await gu.enterCell('123');
await gu.getCell('B', 1).click();
await enterCellWithoutWaitingOnServer('124');
const toast = await driver.wait(() => gu.getToasts(), 10000);
assert.include(toast, 'New changes are temporarily suspended. Webhooks queue overflowed.' +
' Please check webhooks settings, remove invalid webhooks, and clean the queue.\ngo to webhook settings');
await gu.waitToPass(async () => {
const toast = await gu.getToasts();
assert.include(toast, 'New changes are temporarily suspended. Webhooks queue overflowed.' +
' Please check webhooks settings, remove invalid webhooks, and clean the queue.\ngo to webhook settings');
}, 4000);
});
it('message should disappear after clearing queue', async function () {
await openWebhookPageWithoutWaitForServer();
await driver.findContent('button', /Clear Queue/).click();
await gu.waitForServer();
await waitForOverflownMessageToDisappear();
const toast = await driver.wait(() => gu.getToasts());
assert.notInclude(toast, 'New changes are temporarily suspended. Webhooks queue overflowed.' +
' Please check webhooks settings, remove invalid webhooks, and clean the queue.\ngo to webhook settings');
await gu.waitToPass(async () => {
const toast = await gu.getToasts();
assert.notInclude(toast, 'New changes are temporarily suspended. Webhooks queue overflowed.' +
' Please check webhooks settings, remove invalid webhooks, and clean the queue.\ngo to webhook settings');
}, 12500);
});
});
async function waitForOverflownMessageToDisappear(maxWait = 12500) {
await driver.wait(async () => {
try {
for (;;) {
const toasts = await gu.getToasts();
const filteredToasts = toasts.find(t => t=='New changes are temporarily suspended. Webhooks queue overflowed.' +
' Please check webhooks settings, remove invalid webhooks, and clean the queue.\ngo to webhook settings');
if (!filteredToasts) {
return true;
}
}
} catch (e) {
return false;
}
}, maxWait, 'Overflown message did not disappear');
}
async function openWebhookPageWithoutWaitForServer() {
await openDocumentSettings();
const button = await driver.findContentWait('a', /Manage Webhooks/, 3000);

Loading…
Cancel
Save