From 51a34835c59077b7d035c194338b4a5555cbcfb3 Mon Sep 17 00:00:00 2001 From: Dmitry S Date: Tue, 18 Jun 2024 09:56:11 -0400 Subject: [PATCH] (core) Disable formula timing UI for non-owners Summary: For non-owners, the timing section of Document Settings is now disabled. For non-editors, the "Reload" section is disabled. Test Plan: Added a test case for timing being disabled. Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D4275 --- app/client/ui/AdminPanelCss.ts | 19 +++++++++++++++++-- app/client/ui/DocumentSettings.ts | 5 +++++ test/nbrowser/Timing.ts | 24 ++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/app/client/ui/AdminPanelCss.ts b/app/client/ui/AdminPanelCss.ts index 9fb8b15f..2853a3fa 100644 --- a/app/client/ui/AdminPanelCss.ts +++ b/app/client/ui/AdminPanelCss.ts @@ -1,3 +1,4 @@ +import {hoverTooltip} from 'app/client/ui/tooltips'; import {transition} from 'app/client/ui/transitions'; import {toggle} from 'app/client/ui2018/checkbox'; import {mediaSmall, testId, theme, vars} from 'app/client/ui2018/cssVars'; @@ -21,6 +22,7 @@ export function AdminSectionItem(owner: IDisposableOwner, options: { description?: DomContents, value?: DomContents, expandedContent?: DomContents, + disabled?: false|string, }) { const itemContent = (...prefix: DomContents[]) => [ cssItemName( @@ -34,7 +36,7 @@ export function AdminSectionItem(owner: IDisposableOwner, options: { testId(`admin-panel-item-value-${options.id}`), dom.on('click', ev => ev.stopPropagation())), ]; - if (options.expandedContent) { + if (options.expandedContent && !options.disabled) { const isCollapsed = Observable.create(owner, true); return cssItem( cssItemShort( @@ -56,7 +58,13 @@ export function AdminSectionItem(owner: IDisposableOwner, options: { ); } else { return cssItem( - cssItemShort(itemContent()), + cssItemShort(itemContent(), + cssItemShort.cls('-disabled', Boolean(options.disabled)), + options.disabled ? hoverTooltip(options.disabled, { + placement: 'bottom-end', + modifiers: {offset: {offset: '0, -10'}}, + }) : null, + ), testId(`admin-panel-item-${options.id}`), ); } @@ -109,6 +117,9 @@ const cssItemShort = styled('div', ` &-expandable:hover { background-color: ${theme.lightHover}; } + &-disabled { + opacity: .5; + } @container line (max-width: 500px) { & { @@ -157,6 +168,10 @@ const cssItemValue = styled('div', ` margin: -16px; padding: 16px; cursor: auto; + + .${cssItemShort.className}-disabled & { + pointer-events: none; + } `); const cssCollapseIcon = styled(icon, ` diff --git a/app/client/ui/DocumentSettings.ts b/app/client/ui/DocumentSettings.ts index 615292e7..b2c9ba28 100644 --- a/app/client/ui/DocumentSettings.ts +++ b/app/client/ui/DocumentSettings.ts @@ -28,6 +28,7 @@ import {EngineCode} from 'app/common/DocumentSettings'; import {commonUrls, GristLoadConfig} from 'app/common/gristUrls'; import {not, propertyCompare} from 'app/common/gutil'; import {getCurrency, locales} from 'app/common/Locales'; +import {isOwner, isOwnerOrEditor} from 'app/common/roles'; import {Computed, Disposable, dom, fromKo, IDisposableOwner, makeTestId, Observable, styled} from 'grainjs'; import * as moment from 'moment-timezone'; @@ -58,6 +59,8 @@ export class DocSettingsPage extends Disposable { const canChangeEngine = getSupportedEngineChoices().length > 0; const docPageModel = this._gristDoc.docPageModel; const isTimingOn = this._gristDoc.isTimingOn; + const isDocOwner = isOwner(docPageModel.currentDoc.get()); + const isDocEditor = isOwnerOrEditor(docPageModel.currentDoc.get()); return cssContainer( dom.create(AdminSection, t('Document Settings'), [ @@ -115,6 +118,7 @@ export class DocSettingsPage extends Disposable { 'This allows diagnosing which formulas are responsible for slow performance when a ' + 'document is first opened, or when a document responds to changes.' )), + disabled: isDocOwner ? false : t('Only available to document owners'), }), dom.create(AdminSectionItem, { @@ -122,6 +126,7 @@ export class DocSettingsPage extends Disposable { name: t('Reload'), description: t('Hard reset of data engine'), value: cssSmallButton(t('Reload data engine'), dom.on('click', this._reloadEngine.bind(this, true))), + disabled: isDocEditor ? false : t('Only available to document editors'), }), canChangeEngine ? dom.create(AdminSectionItem, { diff --git a/test/nbrowser/Timing.ts b/test/nbrowser/Timing.ts index b21e1304..9659cdf4 100644 --- a/test/nbrowser/Timing.ts +++ b/test/nbrowser/Timing.ts @@ -166,6 +166,30 @@ describe("Timing", function () { await driver.findWait('.test-raw-data-list', 2000); assert.deepEqual(await driver.findAll('.test-raw-data-table-id', e => e.getText()), ['Table1']); }); + + it('should be disabled for non-owners', async function() { + await userApi.updateDocPermissions(docId, {users: { + [gu.translateUser('user2').email]: 'editors', + }}); + + const session = await gu.session().teamSite.user('user2').login(); + await session.loadDoc(`/doc/${docId}`); + await gu.openDocumentSettings(); + + const start = driver.find('.test-settings-timing-start'); + assert.equal(await start.isPresent(), true); + + // Check that we have an informative tooltip. + await start.mouseMove(); + assert.match(await driver.findWait('.test-tooltip', 2000).getText(), /Only available to document owners/); + + // Nothing should happen on click. We click the location rather than the element, since the + // element isn't actually clickable. + await start.mouseMove(); + await driver.withActions(a => a.press().release()); + await driver.sleep(100); + assert.equal(await driver.find(".test-settings-timing-modal").isPresent(), false); + }); }); const element = (testId: string) => ({