mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Adds dots menu to access rules page item
Summary: In Access rules page item, adds “…” buttons that shows a menu of users to view-as: Test Plan: Include new nbrowser test Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D3751
This commit is contained in:
parent
e170eef1a4
commit
b7f65ff408
@ -4,18 +4,20 @@ import {createUserImage} from 'app/client/ui/UserImage';
|
|||||||
import {cssMemberImage, cssMemberListItem, cssMemberPrimary,
|
import {cssMemberImage, cssMemberListItem, cssMemberPrimary,
|
||||||
cssMemberSecondary, cssMemberText} from 'app/client/ui/UserItem';
|
cssMemberSecondary, cssMemberText} from 'app/client/ui/UserItem';
|
||||||
import {testId, theme, vars} from 'app/client/ui2018/cssVars';
|
import {testId, theme, vars} from 'app/client/ui2018/cssVars';
|
||||||
import {menuCssClass} from 'app/client/ui2018/menus';
|
|
||||||
import {PermissionDataWithExtraUsers} from 'app/common/ActiveDocAPI';
|
import {PermissionDataWithExtraUsers} from 'app/common/ActiveDocAPI';
|
||||||
|
import {menu, menuCssClass, menuItemLink} from 'app/client/ui2018/menus';
|
||||||
import {userOverrideParams} from 'app/common/gristUrls';
|
import {userOverrideParams} from 'app/common/gristUrls';
|
||||||
import {FullUser} from 'app/common/LoginSessionAPI';
|
import {FullUser} from 'app/common/LoginSessionAPI';
|
||||||
import {ANONYMOUS_USER_EMAIL, EVERYONE_EMAIL} from 'app/common/UserAPI';
|
import {ANONYMOUS_USER_EMAIL, EVERYONE_EMAIL} from 'app/common/UserAPI';
|
||||||
import {getRealAccess, UserAccessData} from 'app/common/UserAPI';
|
import {getRealAccess, UserAccessData} from 'app/common/UserAPI';
|
||||||
import {Disposable, dom, Observable, styled} from 'grainjs';
|
import {Disposable, dom, Observable, styled} from 'grainjs';
|
||||||
import {cssMenu, cssMenuWrap, defaultMenuOptions, IPopupOptions, setPopupToCreateDom} from 'popweasel';
|
import {cssMenu, cssMenuWrap, defaultMenuOptions, IMenuOptions, IPopupOptions, setPopupToCreateDom} from 'popweasel';
|
||||||
import {getUserRoleText} from 'app/common/UserAPI';
|
import {getUserRoleText} from 'app/common/UserAPI';
|
||||||
import {makeT} from 'app/client/lib/localization';
|
import {makeT} from 'app/client/lib/localization';
|
||||||
|
import {waitGrainObs} from 'app/common/gutil';
|
||||||
|
import noop from 'lodash/noop';
|
||||||
|
|
||||||
const t = makeT("ACLUsers");
|
const t = makeT("ViewAsDropdown");
|
||||||
|
|
||||||
function isSpecialEmail(email: string) {
|
function isSpecialEmail(email: string) {
|
||||||
return email === ANONYMOUS_USER_EMAIL || email === EVERYONE_EMAIL;
|
return email === ANONYMOUS_USER_EMAIL || email === EVERYONE_EMAIL;
|
||||||
@ -23,15 +25,33 @@ function isSpecialEmail(email: string) {
|
|||||||
|
|
||||||
export class ACLUsersPopup extends Disposable {
|
export class ACLUsersPopup extends Disposable {
|
||||||
public readonly isInitialized = Observable.create(this, false);
|
public readonly isInitialized = Observable.create(this, false);
|
||||||
|
public readonly allUsers = Observable.create<UserAccessData[]>(this, []);
|
||||||
private _shareUsers: UserAccessData[] = []; // Users doc is shared with.
|
private _shareUsers: UserAccessData[] = []; // Users doc is shared with.
|
||||||
private _attributeTableUsers: UserAccessData[] = []; // Users mentioned in attribute tables.
|
private _attributeTableUsers: UserAccessData[] = []; // Users mentioned in attribute tables.
|
||||||
private _exampleUsers: UserAccessData[] = []; // Example users.
|
private _exampleUsers: UserAccessData[] = []; // Example users.
|
||||||
private _currentUser: FullUser|null = null;
|
private _currentUser: FullUser|null = null;
|
||||||
private _pageModel: DocPageModel|null = null;
|
|
||||||
|
|
||||||
public init(pageModel: DocPageModel, permissionData: PermissionDataWithExtraUsers|null) {
|
constructor(public pageModel: DocPageModel,
|
||||||
|
public fetch: () => Promise<PermissionDataWithExtraUsers|null> = () => this._fetchData()) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async load() {
|
||||||
|
const permissionData = await this.fetch();
|
||||||
|
if (this.isDisposed()) { return; }
|
||||||
|
this.init(permissionData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUsers() {
|
||||||
|
const users = [...this._shareUsers, ...this._attributeTableUsers];
|
||||||
|
if (this._showExampleUsers()) { users.push(...this._exampleUsers); }
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(permissionData: PermissionDataWithExtraUsers|null) {
|
||||||
|
const pageModel = this.pageModel;
|
||||||
this._currentUser = pageModel.userOverride.get()?.user || pageModel.appModel.currentValidUser;
|
this._currentUser = pageModel.userOverride.get()?.user || pageModel.appModel.currentValidUser;
|
||||||
this._pageModel = pageModel;
|
|
||||||
if (permissionData) {
|
if (permissionData) {
|
||||||
this._shareUsers = permissionData.users.map(user => ({
|
this._shareUsers = permissionData.users.map(user => ({
|
||||||
...user,
|
...user,
|
||||||
@ -41,6 +61,7 @@ export class ACLUsersPopup extends Disposable {
|
|||||||
.filter(user => this._currentUser?.id !== user.id);
|
.filter(user => this._currentUser?.id !== user.id);
|
||||||
this._attributeTableUsers = permissionData.attributeTableUsers;
|
this._attributeTableUsers = permissionData.attributeTableUsers;
|
||||||
this._exampleUsers = permissionData.exampleUsers;
|
this._exampleUsers = permissionData.exampleUsers;
|
||||||
|
this.allUsers.set(this.getUsers());
|
||||||
this.isInitialized.set(true);
|
this.isInitialized.set(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -61,7 +82,7 @@ export class ACLUsersPopup extends Disposable {
|
|||||||
// Include example users only if there are not many "real" users.
|
// Include example users only if there are not many "real" users.
|
||||||
// It might be better to have an expandable section with these users, collapsed
|
// It might be better to have an expandable section with these users, collapsed
|
||||||
// by default, but that's beyond my UI ken.
|
// by default, but that's beyond my UI ken.
|
||||||
(this._shareUsers.length + this._attributeTableUsers.length < 5) ? [
|
this._showExampleUsers() ? [
|
||||||
(this._exampleUsers.length > 0) ? cssHeader(t("Example Users")) : null,
|
(this._exampleUsers.length > 0) ? cssHeader(t("Example Users")) : null,
|
||||||
dom.forEach(this._exampleUsers, buildExampleUserRow)
|
dom.forEach(this._exampleUsers, buildExampleUserRow)
|
||||||
] : null,
|
] : null,
|
||||||
@ -71,6 +92,30 @@ export class ACLUsersPopup extends Disposable {
|
|||||||
}, {...defaultMenuOptions, ...options});
|
}, {...defaultMenuOptions, ...options});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public menu(options: IMenuOptions) {
|
||||||
|
return menu(() => {
|
||||||
|
this.load().catch(noop);
|
||||||
|
return [
|
||||||
|
cssMenuHeader('view as'),
|
||||||
|
dom.forEach(this.allUsers, user => menuItemLink(
|
||||||
|
`${user.name || user.email} (${getUserRoleText(user)})`,
|
||||||
|
testId('acl-user-access'),
|
||||||
|
this._viewAs(user),
|
||||||
|
)),
|
||||||
|
];
|
||||||
|
}, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _fetchData() {
|
||||||
|
const doc = this.pageModel.currentDoc.get();
|
||||||
|
const gristDoc = await waitGrainObs(this.pageModel.gristDoc);
|
||||||
|
return doc && gristDoc.docComm.getUsersForViewAs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _showExampleUsers() {
|
||||||
|
return this._shareUsers.length + this._attributeTableUsers.length < 5;
|
||||||
|
}
|
||||||
|
|
||||||
private _buildUserRow(user: UserAccessData, opt: {isExampleUser?: boolean} = {}) {
|
private _buildUserRow(user: UserAccessData, opt: {isExampleUser?: boolean} = {}) {
|
||||||
return dom('a',
|
return dom('a',
|
||||||
{class: cssMemberListItem.className + ' ' + cssUserItem.className},
|
{class: cssMemberListItem.className + ' ' + cssUserItem.className},
|
||||||
@ -89,15 +134,15 @@ export class ACLUsersPopup extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private _viewAs(user: UserAccessData) {
|
private _viewAs(user: UserAccessData) {
|
||||||
if (this._pageModel?.isPrefork.get() &&
|
if (this.pageModel?.isPrefork.get() &&
|
||||||
this._pageModel?.currentDoc.get()?.access !== 'owners') {
|
this.pageModel?.currentDoc.get()?.access !== 'owners') {
|
||||||
// "View As" is restricted to document owners on the back-end. Non-owners can be
|
// "View As" is restricted to document owners on the back-end. Non-owners can be
|
||||||
// permitted to pretend to be owners of a pre-forked document, but if they want
|
// permitted to pretend to be owners of a pre-forked document, but if they want
|
||||||
// to do "View As", that would be layering pretence over pretense. Better to just
|
// to do "View As", that would be layering pretence over pretense. Better to just
|
||||||
// go ahead and create the fork, so the user becomes a genuine owner, so the
|
// go ahead and create the fork, so the user becomes a genuine owner, so the
|
||||||
// back-end doesn't have to become too metaphysical (and maybe hard to review).
|
// back-end doesn't have to become too metaphysical (and maybe hard to review).
|
||||||
return dom.on('click', async () => {
|
return dom.on('click', async () => {
|
||||||
const forkResult = await this._pageModel?.gristDoc.get()?.docComm.fork();
|
const forkResult = await this.pageModel?.gristDoc.get()?.docComm.fork();
|
||||||
if (!forkResult) { throw new Error('Failed to create fork'); }
|
if (!forkResult) { throw new Error('Failed to create fork'); }
|
||||||
window.location.assign(urlState().makeUrl(userOverrideParams(user.email,
|
window.location.assign(urlState().makeUrl(userOverrideParams(user.email,
|
||||||
{doc: forkResult.urlId,
|
{doc: forkResult.urlId,
|
||||||
@ -139,3 +184,12 @@ const cssHeader = styled('div', `
|
|||||||
font-size: ${vars.xsmallFontSize};
|
font-size: ${vars.xsmallFontSize};
|
||||||
color: ${theme.darkText};
|
color: ${theme.darkText};
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const cssMenuHeader = styled('div', `
|
||||||
|
margin: 8px 24px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: ${vars.xsmallFontSize};
|
||||||
|
color: ${theme.darkText};
|
||||||
|
`);
|
||||||
|
@ -122,7 +122,7 @@ export class AccessRules extends Disposable {
|
|||||||
// Map of tableId to basic metadata for all tables in the document.
|
// Map of tableId to basic metadata for all tables in the document.
|
||||||
private _aclResources = new Map<string, AclTableDescription>();
|
private _aclResources = new Map<string, AclTableDescription>();
|
||||||
|
|
||||||
private _aclUsersPopup = ACLUsersPopup.create(this);
|
private _aclUsersPopup = ACLUsersPopup.create(this, this._gristDoc.docPageModel);
|
||||||
|
|
||||||
constructor(private _gristDoc: GristDoc) {
|
constructor(private _gristDoc: GristDoc) {
|
||||||
super();
|
super();
|
||||||
@ -531,13 +531,7 @@ export class AccessRules extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _updateDocAccessData() {
|
private async _updateDocAccessData() {
|
||||||
const pageModel = this._gristDoc.docPageModel;
|
await this._aclUsersPopup.load();
|
||||||
const doc = pageModel.currentDoc.get();
|
|
||||||
|
|
||||||
const permissionData = doc && await this._gristDoc.docComm.getUsersForViewAs();
|
|
||||||
if (this.isDisposed()) { return; }
|
|
||||||
|
|
||||||
this._aclUsersPopup.init(pageModel, permissionData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _addButtonsForMissingTables(buttons: Array<HTMLAnchorElement | HTMLButtonElement>, tableIds: string[]) {
|
private _addButtonsForMissingTables(buttons: Array<HTMLAnchorElement | HTMLButtonElement>, tableIds: string[]) {
|
||||||
@ -625,7 +619,7 @@ class TableRules extends Disposable {
|
|||||||
} else if (this._defRuleSet) {
|
} else if (this._defRuleSet) {
|
||||||
DefaultObsRuleSet.create(this._defaultRuleSet, this._accessRules, this, this._haveColumnRules,
|
DefaultObsRuleSet.create(this._defaultRuleSet, this._accessRules, this, this._haveColumnRules,
|
||||||
this._defRuleSet);
|
this._defRuleSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ruleStatus = Computed.create(this, (use) => {
|
this.ruleStatus = Computed.create(this, (use) => {
|
||||||
const columnRuleSets = use(this._columnRuleSets);
|
const columnRuleSets = use(this._columnRuleSets);
|
||||||
|
@ -21,7 +21,7 @@ const t = makeT('components.ViewAsBanner');
|
|||||||
export class ViewAsBanner extends Disposable {
|
export class ViewAsBanner extends Disposable {
|
||||||
|
|
||||||
private _userOverride = this._docPageModel.userOverride;
|
private _userOverride = this._docPageModel.userOverride;
|
||||||
private _usersPopup = ACLUsersPopup.create(this);
|
private _usersPopup = ACLUsersPopup.create(this, this._docPageModel, this._getUsersForViewAs.bind(this));
|
||||||
|
|
||||||
constructor (private _docPageModel: DocPageModel) {
|
constructor (private _docPageModel: DocPageModel) {
|
||||||
super();
|
super();
|
||||||
@ -73,8 +73,7 @@ export class ViewAsBanner extends Disposable {
|
|||||||
|
|
||||||
private async _initViewAsUsers() {
|
private async _initViewAsUsers() {
|
||||||
await waitGrainObs(this._docPageModel.gristDoc);
|
await waitGrainObs(this._docPageModel.gristDoc);
|
||||||
const permissionData = await this._getUsersForViewAs();
|
await this._usersPopup.load();
|
||||||
this._usersPopup.init(this._docPageModel, permissionData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getUsersForViewAs(): Promise<PermissionDataWithExtraUsers> {
|
private _getUsersForViewAs(): Promise<PermissionDataWithExtraUsers> {
|
||||||
|
@ -7,7 +7,9 @@ import {HomeModel} from 'app/client/models/HomeModel';
|
|||||||
import {getWorkspaceInfo, workspaceName} from 'app/client/models/WorkspaceInfo';
|
import {getWorkspaceInfo, workspaceName} from 'app/client/models/WorkspaceInfo';
|
||||||
import {addNewButton, cssAddNewButton} from 'app/client/ui/AddNewButton';
|
import {addNewButton, cssAddNewButton} from 'app/client/ui/AddNewButton';
|
||||||
import {docImport, importFromPlugin} from 'app/client/ui/HomeImports';
|
import {docImport, importFromPlugin} from 'app/client/ui/HomeImports';
|
||||||
import {cssLinkText, cssPageEntry, cssPageIcon, cssPageLink, cssSpacer} from 'app/client/ui/LeftPanelCommon';
|
import {
|
||||||
|
cssLinkText, cssMenuTrigger, cssPageEntry, cssPageIcon, cssPageLink, cssSpacer
|
||||||
|
} from 'app/client/ui/LeftPanelCommon';
|
||||||
import {createVideoTourToolsButton} from 'app/client/ui/OpenVideoTour';
|
import {createVideoTourToolsButton} from 'app/client/ui/OpenVideoTour';
|
||||||
import {transientInput} from 'app/client/ui/transientInput';
|
import {transientInput} from 'app/client/ui/transientInput';
|
||||||
import {testId, theme} from 'app/client/ui2018/cssVars';
|
import {testId, theme} from 'app/client/ui2018/cssVars';
|
||||||
@ -255,23 +257,3 @@ export const cssEditorInput = styled(transientInput, `
|
|||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const cssMenuTrigger = styled('div', `
|
|
||||||
margin: 0 4px 0 auto;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
padding: 4px;
|
|
||||||
line-height: 0px;
|
|
||||||
border-radius: 3px;
|
|
||||||
cursor: default;
|
|
||||||
display: none;
|
|
||||||
.${cssPageLink.className}:hover > &, &.weasel-popup-open {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
&:hover, &.weasel-popup-open {
|
|
||||||
background-color: ${theme.pageOptionsHoverBg};
|
|
||||||
}
|
|
||||||
.${cssPageEntry.className}-selected &:hover, .${cssPageEntry.className}-selected &.weasel-popup-open {
|
|
||||||
background-color: ${theme.pageOptionsSelectedHoverBg};
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
@ -185,3 +185,23 @@ export const cssPageEntrySmall = styled(cssPageEntry, `
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
export const cssMenuTrigger = styled('div', `
|
||||||
|
margin: 0 4px 0 auto;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
padding: 4px;
|
||||||
|
line-height: 0px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: default;
|
||||||
|
display: none;
|
||||||
|
.${cssPageLink.className}:hover > &, &.weasel-popup-open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
&:hover, &.weasel-popup-open {
|
||||||
|
background-color: ${theme.pageOptionsHoverBg};
|
||||||
|
}
|
||||||
|
.${cssPageEntry.className}-selected &:hover, .${cssPageEntry.className}-selected &.weasel-popup-open {
|
||||||
|
background-color: ${theme.pageOptionsSelectedHoverBg};
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
import {ACLUsersPopup} from 'app/client/aclui/ACLUsers';
|
||||||
import {makeT} from 'app/client/lib/localization';
|
import {makeT} from 'app/client/lib/localization';
|
||||||
import {GristDoc} from 'app/client/components/GristDoc';
|
import {GristDoc} from 'app/client/components/GristDoc';
|
||||||
import {urlState} from 'app/client/models/gristUrlState';
|
import {urlState} from 'app/client/models/gristUrlState';
|
||||||
import {getUserOrgPrefObs, markAsSeen} from 'app/client/models/UserPrefs';
|
import {getUserOrgPrefObs, markAsSeen} from 'app/client/models/UserPrefs';
|
||||||
import {showExampleCard} from 'app/client/ui/ExampleCard';
|
import {showExampleCard} from 'app/client/ui/ExampleCard';
|
||||||
import {buildExamples} from 'app/client/ui/ExampleInfo';
|
import {buildExamples} from 'app/client/ui/ExampleInfo';
|
||||||
import {createHelpTools, cssLinkText, cssPageEntry, cssPageEntryMain, cssPageEntrySmall,
|
import {createHelpTools, cssLinkText, cssMenuTrigger, cssPageEntry, cssPageEntryMain, cssPageEntrySmall,
|
||||||
cssPageIcon, cssPageLink, cssSectionHeader, cssSpacer, cssSplitPageEntry,
|
cssPageIcon, cssPageLink, cssSectionHeader, cssSpacer, cssSplitPageEntry,
|
||||||
cssTools} from 'app/client/ui/LeftPanelCommon';
|
cssTools} from 'app/client/ui/LeftPanelCommon';
|
||||||
import {theme} from 'app/client/ui2018/cssVars';
|
import {theme} from 'app/client/ui2018/cssVars';
|
||||||
@ -12,6 +13,7 @@ import {icon} from 'app/client/ui2018/icons';
|
|||||||
import {confirmModal} from 'app/client/ui2018/modals';
|
import {confirmModal} from 'app/client/ui2018/modals';
|
||||||
import {isOwner} from 'app/common/roles';
|
import {isOwner} from 'app/common/roles';
|
||||||
import {Disposable, dom, makeTestId, Observable, observable, styled} from 'grainjs';
|
import {Disposable, dom, makeTestId, Observable, observable, styled} from 'grainjs';
|
||||||
|
import noop from 'lodash/noop';
|
||||||
|
|
||||||
const testId = makeTestId('test-tools-');
|
const testId = makeTestId('test-tools-');
|
||||||
const t = makeT('Tools');
|
const t = makeT('Tools');
|
||||||
@ -33,11 +35,31 @@ export function tools(owner: Disposable, gristDoc: GristDoc, leftPanelOpen: Obse
|
|||||||
cssPageEntry(
|
cssPageEntry(
|
||||||
cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'acl'),
|
cssPageEntry.cls('-selected', (use) => use(gristDoc.activeViewId) === 'acl'),
|
||||||
cssPageEntry.cls('-disabled', (use) => !use(canViewAccessRules)),
|
cssPageEntry.cls('-disabled', (use) => !use(canViewAccessRules)),
|
||||||
dom.domComputed(canViewAccessRules, (_canViewAccessRules) => {
|
dom.domComputedOwned(canViewAccessRules, (computedOwner, _canViewAccessRules) => {
|
||||||
|
const aclUsers = ACLUsersPopup.create(computedOwner, docPageModel);
|
||||||
|
if (_canViewAccessRules) {
|
||||||
|
aclUsers.load()
|
||||||
|
// getUsersForViewAs() could fail for couple good reasons (access deny to anon user,
|
||||||
|
// `document not found` when anon creates a new empty document, ...), users can have more
|
||||||
|
// info by opening acl page, so let's silently fail here.
|
||||||
|
.catch(noop);
|
||||||
|
}
|
||||||
return cssPageLink(
|
return cssPageLink(
|
||||||
cssPageIcon('EyeShow'),
|
cssPageIcon('EyeShow'),
|
||||||
cssLinkText(t("Access Rules")),
|
cssLinkText(t("Access Rules")),
|
||||||
_canViewAccessRules ? urlState().setLinkUrl({docPage: 'acl'}) : null,
|
_canViewAccessRules ? urlState().setLinkUrl({docPage: 'acl'}) : null,
|
||||||
|
cssMenuTrigger(
|
||||||
|
icon('Dots'),
|
||||||
|
aclUsers.menu({
|
||||||
|
placement: 'bottom-start',
|
||||||
|
parentSelectorToMark: '.' + cssPageEntry.className
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Clicks on the menu trigger shouldn't follow the link that it's contained in.
|
||||||
|
dom.on('click', (ev) => { ev.stopPropagation(); ev.preventDefault(); }),
|
||||||
|
testId('access-rules-trigger'),
|
||||||
|
dom.show(use => use(aclUsers.isInitialized) && _canViewAccessRules),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
testId('access-rules'),
|
testId('access-rules'),
|
||||||
|
@ -9,5 +9,5 @@ export default function getCurrentTime(): moment.Moment {
|
|||||||
if (typeof window === 'undefined' || !window) { return getDefault(); }
|
if (typeof window === 'undefined' || !window) { return getDefault(); }
|
||||||
const searchParams = new URLSearchParams(window.location.search);
|
const searchParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
return searchParams.has('currentTime') ? moment(searchParams.get('currentTime')) : getDefault();
|
return searchParams.has('currentTime') ? moment(searchParams.get('currentTime') || undefined) : getDefault();
|
||||||
}
|
}
|
||||||
|
@ -1667,6 +1667,14 @@ export async function openDocDropdown(docNameOrRow: string|WebElement): Promise<
|
|||||||
await docRow.find('.test-dm-doc-options,.test-dm-pinned-doc-options').mouseMove().click();
|
await docRow.find('.test-dm-doc-options,.test-dm-pinned-doc-options').mouseMove().click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open ⋮ dropdown menu for doc access rules.
|
||||||
|
*/
|
||||||
|
export async function openAccessRulesDropdown(): Promise<void> {
|
||||||
|
await driver.find('.test-tools-access-rules').mouseMove();
|
||||||
|
await driver.find('.test-tools-access-rules-trigger').mouseMove().click();
|
||||||
|
}
|
||||||
|
|
||||||
export async function editOrgAcls(): Promise<void> {
|
export async function editOrgAcls(): Promise<void> {
|
||||||
// To prevent a common flakiness problem, wait for a potentially open modal dialog
|
// To prevent a common flakiness problem, wait for a potentially open modal dialog
|
||||||
// to close before attempting to open the account menu.
|
// to close before attempting to open the account menu.
|
||||||
|
Loading…
Reference in New Issue
Block a user