From a77e82388bbcf37a488ab682cc26c7f293ca7745 Mon Sep 17 00:00:00 2001 From: roleohibachi Date: Tue, 25 Jul 2023 20:49:26 -0600 Subject: [PATCH 1/7] TEXT() alternatives per #580 (#591) --- sandbox/grist/functions/text.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sandbox/grist/functions/text.py b/sandbox/grist/functions/text.py index ea483985..3d9a7c0d 100644 --- a/sandbox/grist/functions/text.py +++ b/sandbox/grist/functions/text.py @@ -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() From bc599916f5c4ded995d18ccd082f78bb0e362ae8 Mon Sep 17 00:00:00 2001 From: Paul Fitzpatrick Date: Wed, 26 Jul 2023 11:20:20 +0100 Subject: [PATCH 2/7] tweak webhook overflow test (#590) This test appears to fail if toasts aren't issued fast enough (perhaps because an empty toast list may count as truthy?). I might be missing something since I don't understand the purpose of waitForOverflownMessageToDisappear apart from lengthening a timeout. --- test/nbrowser/WebhookOverflow.ts | 37 ++++++++++---------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/test/nbrowser/WebhookOverflow.ts b/test/nbrowser/WebhookOverflow.ts index 92c510c7..1550476b 100644 --- a/test/nbrowser/WebhookOverflow.ts +++ b/test/nbrowser/WebhookOverflow.ts @@ -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); From 71c6537c49523ada4673784637b1712689ae1391 Mon Sep 17 00:00:00 2001 From: Paul Fitzpatrick Date: Wed, 26 Jul 2023 13:08:55 +0100 Subject: [PATCH 3/7] tweak a date test that can occasionally fail (#592) A date test was noted to fail, with a formula intended for a cell ending up in the column header. This may help. Entering formulas reliably requires waiting for a particular focus state. --- test/nbrowser/Dates.ntest.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/nbrowser/Dates.ntest.js b/test/nbrowser/Dates.ntest.js index 6c013004..54f13335 100644 --- a/test/nbrowser/Dates.ntest.js +++ b/test/nbrowser/Dates.ntest.js @@ -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('='); From ab6807c3420b2572fd04f47ac953f1c003fcfa57 Mon Sep 17 00:00:00 2001 From: Vincent Viers <30295971+vviers@users.noreply.github.com> Date: Wed, 26 Jul 2023 21:08:04 +0200 Subject: [PATCH 4/7] fix: don't count falses in summary (#589) --- app/client/components/SelectionSummary.ts | 2 +- test/nbrowser/SelectionSummary.ts | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/client/components/SelectionSummary.ts b/app/client/components/SelectionSummary.ts index 2fd3ef04..e4f25c8c 100644 --- a/app/client/components/SelectionSummary.ts +++ b/app/client/components/SelectionSummary.ts @@ -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++; } } diff --git a/test/nbrowser/SelectionSummary.ts b/test/nbrowser/SelectionSummary.ts index 9ede970f..eb72db35 100644 --- a/test/nbrowser/SelectionSummary.ts +++ b/test/nbrowser/SelectionSummary.ts @@ -130,11 +130,6 @@ describe('SelectionSummary', function () { count: 2, sum: null, }); - await selectAndAssert({col: 2, row: 0}, {col: 3, row: 5}, { - dimensions: '6⨯2', - 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: '6⨯2', + 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); From f0b9e1f7e9f8bf01c8239d61b64a830bad4e45dd Mon Sep 17 00:00:00 2001 From: Paul Fitzpatrick Date: Thu, 27 Jul 2023 12:02:17 +0100 Subject: [PATCH 5/7] allow Grist front-end to function when location history is unavailable (#596) * allow Grist front-end to function when location history is unavailable When the Grist front-end is embedded in an iframe, using a srcdoc attribute, history.pushState and similar methods are unavailable. Currently, that makes it impossible to navigate between Grist pages, since an access error is thrown (behavior may be browser dependent). With this change, navigation succeeds. * give unrelated possibly slow test a little more time --- app/client/lib/UrlState.ts | 22 ++++++++++++++++------ test/fixtures/docs/SelectBySummary.grist | Bin 618496 -> 622592 bytes test/nbrowser/SelectByRefList.ts | 2 +- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/app/client/lib/UrlState.ts b/app/client/lib/UrlState.ts index d2487e9e..f2ad4d21 100644 --- a/app/client/lib/UrlState.ts +++ b/app/client/lib/UrlState.ts @@ -77,13 +77,23 @@ export class UrlState 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); } diff --git a/test/fixtures/docs/SelectBySummary.grist b/test/fixtures/docs/SelectBySummary.grist index c7d67c1b1b38743af931561d952e0409a3b63c20..453c483fad3d3776eb1fcdd04de12ae0e9cf850e 100644 GIT binary patch delta 2854 zcmbtVYfKbZ6rOwMb!T_y&MpWmunXhzE(ps$0BzH5u-gsp*fVX-(Ui1R;DT+F&+xCX|Hb=iq?fr%sF8UP=StcPb!2eyd3-R}oSl(9}&2uVE)3W};_S4fR1 z(Y}FrpB8b+-7T+n%d1<~1U9yH%jM;%E*4CfmRKpv9pNnc237O#aCbRBJ4{3nz;8{qf9Kewp&H=l3N}ALrRY?N=72m)U zV>TDu_F_694^Sj zESq}s3^Lf`N!yLvqASaeXC+T#8Lh@5Fbg)rv z=ep2{jmu@CnieNA19Hhf1%M-O(_kqR-#wrjhn%8C;%bE0YGF>^U`&l}PE}eh3bk&BIKzEDW9cXTAkzMnUNT&@H5l=Zhyv1Ev znb<1x_J#(KROXYKY2-(O%xsoCR%nJp5&$?zbs2P|+AH8TdMKIW;_a=iV1MO&Y7dW} zM^qm^EPJf_!(5kK(HD}}wRg9WVF0p>d=94$)xbS?p0b}t~ zy&K0Z{w{s|0g!M~G`&3@A6Qvin;y5@wOD+muff;QSgX~BG$mFW?O(rkXIty0$ol5h zWb`~*O0G;J&f5WaN)UAa#d^PxLafCnZKht-|7u(-Qhe~kUp9z-UaC|B}S*fCv%O`TyU;N_bwo2dUENL zBA3X&K$aZ+4#~5h-ZtmclWXJ1Mcf*EhAf}fSGOYiYWM7_#F&2-#$E|A-@Lyv-i*9{ tbB0_(-k(4$`C=Ls&+v>K$(IiT=xHD_EeRE=%VD9Jez!?r#u)28=|7w#7McJ6 delta 944 zcmZ9KO-PhM7{}+C_hV+rJ=ac10~eiYzw?$=t$>u-MWdC@WJ7Y;&VLL6q0fNc~+fRJl?Ev~2C$+Z|mez)nq_2+g&9SVa+>NVc~n_8jf(?m>3C@7-Dm zrrzJLWQZT*Z}=T9GQJ1LxW#7>F91qg+V&+nSKy$kqgu+Sh+k)e^;_!)Qz@GP|nKgk=28N`Z4;Y>*;jzd^j z8U&oJ8jR=VS0L|Kc8$KGmE^oWtw*(UN`+JCgKt>;h~Pp`*+A?39NQ{}LQv>b&xKm- zoSg^9yYeY#z(4b+{06_kle~#naF+4=UYjBQ8HE@tFn}4^<(D@!j&!ysk92hGY*huj z2)~pGTf6DSNR|hkWQU;xY3ydh8kZ@I21i=rFmfXSAE@jOtgm;|aW6 z-tmf4SBz7btu7%}SsC;%6|0KyZD*J6(BJM(luslgvhd>n zz^o|-%kk{Qg$^ml2NdV3AG*N}93j|fRaL=2)Y({!*MM_s8SbsJMcMJV|GA%%FvLgGg!HfP+TWS>`mg$-s-sGVu~VOcCkhL{&WumU$Deosl$IpjGxP3rMS{ii;rcZ<+8jbqH6 q?rAyYd9oG;v4=? Date: Thu, 27 Jul 2023 14:36:43 +0200 Subject: [PATCH 6/7] reverting whitespace changes in DocPageModel (#594) --- app/client/models/DocPageModel.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/client/models/DocPageModel.ts b/app/client/models/DocPageModel.ts index 9a3474c3..3c711e2d 100644 --- a/app/client/models/DocPageModel.ts +++ b/app/client/models/DocPageModel.ts @@ -117,20 +117,20 @@ 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); public readonly isTutorialTrunk = Computed.create(this, this.currentDoc, - (use, doc) => doc ? doc.isTutorialTrunk : false); + (use, doc) => doc ? doc.isTutorialTrunk : false); public readonly isTutorialFork = Computed.create(this, this.currentDoc, - (use, doc) => doc ? doc.isTutorialFork : false); + (use, doc) => doc ? doc.isTutorialFork : false); public readonly importSources: ImportSource[] = []; @@ -169,7 +169,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)); } } })); @@ -266,7 +266,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}) @@ -301,7 +301,7 @@ It also disables formulas. [{{error}}]", {error: err.message}) comparisonUrlId: string | undefined, linkParameters: Record | undefined): Promise { 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)); @@ -375,7 +375,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)); @@ -399,13 +399,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), From 47ffa93e056cee06d7a7b68c95c53ccf55ac2838 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Jul 2023 11:10:11 -0400 Subject: [PATCH 7/7] automated update to translation keys (#586) Co-authored-by: Paul's Grist Bot --- static/locales/en.client.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/locales/en.client.json b/static/locales/en.client.json index 05f94bfd..d95c6ffb 100644 --- a/static/locales/en.client.json +++ b/static/locales/en.client.json @@ -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.",