mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) updates from grist-core
This commit is contained in:
commit
fe2089710e
@ -142,4 +142,4 @@ ENV \
|
|||||||
|
|
||||||
EXPOSE 8484
|
EXPOSE 8484
|
||||||
|
|
||||||
CMD ./sandbox/run.sh
|
CMD ["./sandbox/run.sh"]
|
||||||
|
@ -293,6 +293,15 @@ GridView.gridCommands = {
|
|||||||
}
|
}
|
||||||
this.cursor.rowIndex(this.cursor.rowIndex() - 1);
|
this.cursor.rowIndex(this.cursor.rowIndex() - 1);
|
||||||
},
|
},
|
||||||
|
cursorLeft: function() {
|
||||||
|
// This conditional exists so that when users have the cursor in the leftmost column but
|
||||||
|
// are not scrolled to the left i.e. in the case of a wide column, pressing left again will
|
||||||
|
// scroll the pane to the left.
|
||||||
|
if (this.cursor.fieldIndex() === 0) {
|
||||||
|
this.scrollPane.scrollLeft = 0;
|
||||||
|
}
|
||||||
|
this.cursor.fieldIndex(this.cursor.fieldIndex() - 1);
|
||||||
|
},
|
||||||
shiftDown: function() { this._shiftSelect({step: 1, direction: 'down'}); },
|
shiftDown: function() { this._shiftSelect({step: 1, direction: 'down'}); },
|
||||||
shiftUp: function() { this._shiftSelect({step: 1, direction: 'up'}); },
|
shiftUp: function() { this._shiftSelect({step: 1, direction: 'up'}); },
|
||||||
shiftRight: function() { this._shiftSelect({step: 1, direction: 'right'}); },
|
shiftRight: function() { this._shiftSelect({step: 1, direction: 'right'}); },
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
margin-left: -16px; /* to include drag handle that shows up on hover */
|
margin-left: -16px; /* to include drag handle that shows up on hover */
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewsection_title, .viewsection_title_font {
|
.viewsection_title, .viewsection_title_font {
|
||||||
|
@ -23,7 +23,7 @@ export const UP_TRIANGLE = '\u25B2';
|
|||||||
export const DOWN_TRIANGLE = '\u25BC';
|
export const DOWN_TRIANGLE = '\u25BC';
|
||||||
|
|
||||||
const EMAIL_RE = new RegExp("^\\w[\\w%+/='-]*(\\.[\\w%+/='-]+)*@([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z" +
|
const EMAIL_RE = new RegExp("^\\w[\\w%+/='-]*(\\.[\\w%+/='-]+)*@([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z" +
|
||||||
"0-9])?\\.)+[A-Za-z]{2,6}$", "u");
|
"0-9])?\\.)+[A-Za-z]{2,24}$", "u");
|
||||||
|
|
||||||
// Returns whether str starts with prefix. (Note that this implementation avoids creating a new
|
// Returns whether str starts with prefix. (Note that this implementation avoids creating a new
|
||||||
// string, and only checks a single location.)
|
// string, and only checks a single location.)
|
||||||
|
@ -813,7 +813,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
.leftJoinAndSelect('org_groups.memberUsers', 'org_member_users');
|
.leftJoinAndSelect('org_groups.memberUsers', 'org_member_users');
|
||||||
const result = await orgQuery.getRawAndEntities();
|
const result = await orgQuery.getRawAndEntities();
|
||||||
if (result.entities.length === 0) {
|
if (result.entities.length === 0) {
|
||||||
// If the query for the doc failed, return the failure result.
|
// If the query for the org failed, return the failure result.
|
||||||
throw new ApiError('org not found', 404);
|
throw new ApiError('org not found', 404);
|
||||||
}
|
}
|
||||||
org = result.entities[0];
|
org = result.entities[0];
|
||||||
@ -1073,7 +1073,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
needRealOrg: true
|
needRealOrg: true
|
||||||
});
|
});
|
||||||
orgQuery = this._addFeatures(orgQuery);
|
orgQuery = this._addFeatures(orgQuery);
|
||||||
const orgQueryResult = await verifyIsPermitted(orgQuery);
|
const orgQueryResult = await verifyEntity(orgQuery);
|
||||||
const org: Organization = this.unwrapQueryResult(orgQueryResult);
|
const org: Organization = this.unwrapQueryResult(orgQueryResult);
|
||||||
const productFeatures = org.billingAccount.product.features;
|
const productFeatures = org.billingAccount.product.features;
|
||||||
|
|
||||||
@ -1589,7 +1589,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
markPermissions,
|
markPermissions,
|
||||||
needRealOrg: true
|
needRealOrg: true
|
||||||
});
|
});
|
||||||
const queryResult = await verifyIsPermitted(orgQuery);
|
const queryResult = await verifyEntity(orgQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the workspace failed, return the failure result.
|
// If the query for the workspace failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -1657,7 +1657,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
.leftJoinAndSelect('docs.aclRules', 'doc_acl_rules')
|
.leftJoinAndSelect('docs.aclRules', 'doc_acl_rules')
|
||||||
.leftJoinAndSelect('doc_acl_rules.group', 'doc_group')
|
.leftJoinAndSelect('doc_acl_rules.group', 'doc_group')
|
||||||
.leftJoinAndSelect('orgs.billingAccount', 'billing_accounts');
|
.leftJoinAndSelect('orgs.billingAccount', 'billing_accounts');
|
||||||
const queryResult = await verifyIsPermitted(orgQuery);
|
const queryResult = await verifyEntity(orgQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the org failed, return the failure result.
|
// If the query for the org failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -1710,7 +1710,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
.leftJoinAndSelect('acl_rules.group', 'org_group')
|
.leftJoinAndSelect('acl_rules.group', 'org_group')
|
||||||
.leftJoinAndSelect('orgs.workspaces', 'workspaces'); // we may want to count workspaces.
|
.leftJoinAndSelect('orgs.workspaces', 'workspaces'); // we may want to count workspaces.
|
||||||
orgQuery = this._addFeatures(orgQuery); // add features to access optional workspace limit.
|
orgQuery = this._addFeatures(orgQuery); // add features to access optional workspace limit.
|
||||||
const queryResult = await verifyIsPermitted(orgQuery);
|
const queryResult = await verifyEntity(orgQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the organization failed, return the failure result.
|
// If the query for the organization failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -1750,7 +1750,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
manager,
|
manager,
|
||||||
markPermissions: Permissions.UPDATE
|
markPermissions: Permissions.UPDATE
|
||||||
});
|
});
|
||||||
const queryResult = await verifyIsPermitted(wsQuery);
|
const queryResult = await verifyEntity(wsQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the workspace failed, return the failure result.
|
// If the query for the workspace failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -1782,7 +1782,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
.leftJoinAndSelect('docs.aclRules', 'doc_acl_rules')
|
.leftJoinAndSelect('docs.aclRules', 'doc_acl_rules')
|
||||||
.leftJoinAndSelect('doc_acl_rules.group', 'doc_groups')
|
.leftJoinAndSelect('doc_acl_rules.group', 'doc_groups')
|
||||||
.leftJoinAndSelect('workspaces.org', 'orgs');
|
.leftJoinAndSelect('workspaces.org', 'orgs');
|
||||||
const queryResult = await verifyIsPermitted(wsQuery);
|
const queryResult = await verifyEntity(wsQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the workspace failed, return the failure result.
|
// If the query for the workspace failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -1835,7 +1835,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
.leftJoinAndSelect('workspaces.aclRules', 'acl_rules')
|
.leftJoinAndSelect('workspaces.aclRules', 'acl_rules')
|
||||||
.leftJoinAndSelect('acl_rules.group', 'workspace_group');
|
.leftJoinAndSelect('acl_rules.group', 'workspace_group');
|
||||||
wsQuery = this._addFeatures(wsQuery);
|
wsQuery = this._addFeatures(wsQuery);
|
||||||
const queryResult = await verifyIsPermitted(wsQuery);
|
const queryResult = await verifyEntity(wsQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the organization failed, return the failure result.
|
// If the query for the organization failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -2009,7 +2009,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
markPermissions,
|
markPermissions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const queryResult = await verifyIsPermitted(query);
|
const queryResult = await verifyEntity(query);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the doc or fork failed, return the failure result.
|
// If the query for the doc or fork failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -2062,7 +2062,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
manager,
|
manager,
|
||||||
allowSpecialPermit: true,
|
allowSpecialPermit: true,
|
||||||
});
|
});
|
||||||
const queryResult = await verifyIsPermitted(forkQuery);
|
const queryResult = await verifyEntity(forkQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the fork failed, return the failure result.
|
// If the query for the fork failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -2080,7 +2080,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
// Join the workspace and org to get their ids.
|
// Join the workspace and org to get their ids.
|
||||||
.leftJoinAndSelect('docs.aclRules', 'acl_rules')
|
.leftJoinAndSelect('docs.aclRules', 'acl_rules')
|
||||||
.leftJoinAndSelect('acl_rules.group', 'groups');
|
.leftJoinAndSelect('acl_rules.group', 'groups');
|
||||||
const queryResult = await verifyIsPermitted(docQuery);
|
const queryResult = await verifyEntity(docQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the doc failed, return the failure result.
|
// If the query for the doc failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -2218,7 +2218,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
.leftJoinAndSelect('org_groups.memberUsers', 'org_member_users');
|
.leftJoinAndSelect('org_groups.memberUsers', 'org_member_users');
|
||||||
orgQuery = this._addFeatures(orgQuery);
|
orgQuery = this._addFeatures(orgQuery);
|
||||||
orgQuery = this._withAccess(orgQuery, userId, 'orgs');
|
orgQuery = this._withAccess(orgQuery, userId, 'orgs');
|
||||||
const queryResult = await verifyIsPermitted(orgQuery);
|
const queryResult = await verifyEntity(orgQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the organization failed, return the failure result.
|
// If the query for the organization failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -2279,7 +2279,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
.leftJoinAndSelect('org_groups.memberUsers', 'org_users');
|
.leftJoinAndSelect('org_groups.memberUsers', 'org_users');
|
||||||
wsQuery = this._addFeatures(wsQuery, 'org');
|
wsQuery = this._addFeatures(wsQuery, 'org');
|
||||||
wsQuery = this._withAccess(wsQuery, userId, 'workspaces');
|
wsQuery = this._withAccess(wsQuery, userId, 'workspaces');
|
||||||
const queryResult = await verifyIsPermitted(wsQuery);
|
const queryResult = await verifyEntity(wsQuery);
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the workspace failed, return the failure result.
|
// If the query for the workspace failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -2380,17 +2380,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
|
|
||||||
// Returns UserAccessData for all users with any permissions on the org.
|
// Returns UserAccessData for all users with any permissions on the org.
|
||||||
public async getOrgAccess(scope: Scope, orgKey: string|number): Promise<QueryResult<PermissionData>> {
|
public async getOrgAccess(scope: Scope, orgKey: string|number): Promise<QueryResult<PermissionData>> {
|
||||||
const orgQuery = this.org(scope, orgKey, {
|
const queryResult = await this._getOrgWithACLRules(scope, orgKey);
|
||||||
markPermissions: Permissions.VIEW,
|
|
||||||
needRealOrg: true,
|
|
||||||
allowSpecialPermit: true
|
|
||||||
})
|
|
||||||
// Join the org's ACL rules (with 1st level groups/users listed).
|
|
||||||
.leftJoinAndSelect('orgs.aclRules', 'acl_rules')
|
|
||||||
.leftJoinAndSelect('acl_rules.group', 'org_groups')
|
|
||||||
.leftJoinAndSelect('org_groups.memberUsers', 'org_member_users')
|
|
||||||
.leftJoinAndSelect('org_member_users.logins', 'user_logins');
|
|
||||||
const queryResult = await verifyIsPermitted(orgQuery);
|
|
||||||
if (queryResult.status !== 200) {
|
if (queryResult.status !== 200) {
|
||||||
// If the query for the doc failed, return the failure result.
|
// If the query for the doc failed, return the failure result.
|
||||||
return queryResult;
|
return queryResult;
|
||||||
@ -2419,33 +2409,41 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
// maxInheritedRole set on the workspace. Note that information for all users in the org
|
// maxInheritedRole set on the workspace. Note that information for all users in the org
|
||||||
// is given to indicate which users have access to the org but not to this particular workspace.
|
// is given to indicate which users have access to the org but not to this particular workspace.
|
||||||
public async getWorkspaceAccess(scope: Scope, wsId: number): Promise<QueryResult<PermissionData>> {
|
public async getWorkspaceAccess(scope: Scope, wsId: number): Promise<QueryResult<PermissionData>> {
|
||||||
const wsQuery = this._workspace(scope, wsId, {
|
// Run the query for the workspace and org in a transaction. This brings some isolation protection
|
||||||
markPermissions: Permissions.VIEW
|
// against changes to the workspace or org while we are querying.
|
||||||
})
|
const { workspace, org, queryFailure } = await this._connection.transaction(async manager => {
|
||||||
// Join the workspace's ACL rules (with 1st level groups/users listed).
|
const wsQueryResult = await this._getWorkspaceWithACLRules(scope, wsId, { manager });
|
||||||
.leftJoinAndSelect('workspaces.aclRules', 'acl_rules')
|
if (wsQueryResult.status !== 200) {
|
||||||
.leftJoinAndSelect('acl_rules.group', 'workspace_groups')
|
// If the query for the workspace failed, return the failure result.
|
||||||
.leftJoinAndSelect('workspace_groups.memberUsers', 'workspace_group_users')
|
return { queryFailure: wsQueryResult };
|
||||||
.leftJoinAndSelect('workspace_groups.memberGroups', 'workspace_group_groups')
|
|
||||||
.leftJoinAndSelect('workspace_group_users.logins', 'workspace_user_logins')
|
|
||||||
// Join the org and groups/users.
|
|
||||||
.leftJoinAndSelect('workspaces.org', 'org')
|
|
||||||
.leftJoinAndSelect('org.aclRules', 'org_acl_rules')
|
|
||||||
.leftJoinAndSelect('org_acl_rules.group', 'org_groups')
|
|
||||||
.leftJoinAndSelect('org_groups.memberUsers', 'org_group_users')
|
|
||||||
.leftJoinAndSelect('org_group_users.logins', 'org_user_logins');
|
|
||||||
const queryResult = await verifyIsPermitted(wsQuery);
|
|
||||||
if (queryResult.status !== 200) {
|
|
||||||
// If the query for the doc failed, return the failure result.
|
|
||||||
return queryResult;
|
|
||||||
}
|
}
|
||||||
const workspace: Workspace = queryResult.data;
|
|
||||||
|
const orgQuery = this._buildOrgWithACLRulesQuery(scope, wsQueryResult.data.org.id, { manager });
|
||||||
|
const orgQueryResult = await verifyEntity(orgQuery, { skipPermissionCheck: true });
|
||||||
|
if (orgQueryResult.status !== 200) {
|
||||||
|
// If the query for the org failed, return the failure result.
|
||||||
|
return { queryFailure: orgQueryResult };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
workspace: wsQueryResult.data,
|
||||||
|
org: orgQueryResult.data
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (queryFailure) {
|
||||||
|
return queryFailure;
|
||||||
|
}
|
||||||
|
|
||||||
const wsMap = getMemberUserRoles(workspace, this.defaultCommonGroupNames);
|
const wsMap = getMemberUserRoles(workspace, this.defaultCommonGroupNames);
|
||||||
|
|
||||||
|
// Also fetch the organization ACLs so we can determine inherited rights.
|
||||||
|
|
||||||
// The orgMap gives the org access inherited by each user.
|
// The orgMap gives the org access inherited by each user.
|
||||||
const orgMap = getMemberUserRoles(workspace.org, this.defaultBasicGroupNames);
|
const orgMap = getMemberUserRoles(org, this.defaultBasicGroupNames);
|
||||||
const orgMapWithMembership = getMemberUserRoles(workspace.org, this.defaultGroupNames);
|
const orgMapWithMembership = getMemberUserRoles(org, this.defaultGroupNames);
|
||||||
// Iterate through the org since all users will be in the org.
|
// Iterate through the org since all users will be in the org.
|
||||||
const users: UserAccessData[] = getResourceUsers([workspace, workspace.org]).map(u => {
|
|
||||||
|
const users: UserAccessData[] = getResourceUsers([workspace, org]).map(u => {
|
||||||
const orgAccess = orgMapWithMembership[u.id] || null;
|
const orgAccess = orgMapWithMembership[u.id] || null;
|
||||||
return {
|
return {
|
||||||
...this.makeFullUser(u),
|
...this.makeFullUser(u),
|
||||||
@ -2576,7 +2574,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
.leftJoinAndSelect('orgs.aclRules', 'org_acl_rules')
|
.leftJoinAndSelect('orgs.aclRules', 'org_acl_rules')
|
||||||
.leftJoinAndSelect('org_acl_rules.group', 'org_groups')
|
.leftJoinAndSelect('org_acl_rules.group', 'org_groups')
|
||||||
.leftJoinAndSelect('org_groups.memberUsers', 'org_users');
|
.leftJoinAndSelect('org_groups.memberUsers', 'org_users');
|
||||||
const docQueryResult = await verifyIsPermitted(docQuery);
|
const docQueryResult = await verifyEntity(docQuery);
|
||||||
if (docQueryResult.status !== 200) {
|
if (docQueryResult.status !== 200) {
|
||||||
// If the query for the doc failed, return the failure result.
|
// If the query for the doc failed, return the failure result.
|
||||||
return docQueryResult;
|
return docQueryResult;
|
||||||
@ -2603,7 +2601,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
.leftJoinAndSelect('org_acl_rules.group', 'org_groups')
|
.leftJoinAndSelect('org_acl_rules.group', 'org_groups')
|
||||||
.leftJoinAndSelect('org_groups.memberUsers', 'org_users');
|
.leftJoinAndSelect('org_groups.memberUsers', 'org_users');
|
||||||
wsQuery = this._addFeatures(wsQuery);
|
wsQuery = this._addFeatures(wsQuery);
|
||||||
const wsQueryResult = await verifyIsPermitted(wsQuery);
|
const wsQueryResult = await verifyEntity(wsQuery);
|
||||||
if (wsQueryResult.status !== 200) {
|
if (wsQueryResult.status !== 200) {
|
||||||
// If the query for the organization failed, return the failure result.
|
// If the query for the organization failed, return the failure result.
|
||||||
return wsQueryResult;
|
return wsQueryResult;
|
||||||
@ -2681,7 +2679,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
manager
|
manager
|
||||||
})
|
})
|
||||||
.addSelect(this._markIsPermitted('orgs', scope.userId, 'open', permissions), 'is_permitted');
|
.addSelect(this._markIsPermitted('orgs', scope.userId, 'open', permissions), 'is_permitted');
|
||||||
const docQueryResult = await verifyIsPermitted(docQuery);
|
const docQueryResult = await verifyEntity(docQuery);
|
||||||
if (docQueryResult.status !== 200) {
|
if (docQueryResult.status !== 200) {
|
||||||
// If the query for the doc failed, return the failure result.
|
// If the query for the doc failed, return the failure result.
|
||||||
return docQueryResult;
|
return docQueryResult;
|
||||||
@ -4669,7 +4667,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
// SQL results are flattened, and multiplying the number of rows we have already
|
// SQL results are flattened, and multiplying the number of rows we have already
|
||||||
// by the number of workspace users could get excessive.
|
// by the number of workspace users could get excessive.
|
||||||
.leftJoinAndSelect('docs.workspace', 'workspace');
|
.leftJoinAndSelect('docs.workspace', 'workspace');
|
||||||
const queryResult = await verifyIsPermitted(docQuery);
|
const queryResult = await verifyEntity(docQuery);
|
||||||
const doc: Document = this.unwrapQueryResult(queryResult);
|
const doc: Document = this.unwrapQueryResult(queryResult);
|
||||||
|
|
||||||
// Load the workspace's member groups/users.
|
// Load the workspace's member groups/users.
|
||||||
@ -4777,7 +4775,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
manager,
|
manager,
|
||||||
markPermissions: Permissions.REMOVE
|
markPermissions: Permissions.REMOVE
|
||||||
});
|
});
|
||||||
const workspace: Workspace = this.unwrapQueryResult(await verifyIsPermitted(wsQuery));
|
const workspace: Workspace = this.unwrapQueryResult(await verifyEntity(wsQuery));
|
||||||
await manager.createQueryBuilder()
|
await manager.createQueryBuilder()
|
||||||
.update(Workspace).set({removedAt}).where({id: workspace.id})
|
.update(Workspace).set({removedAt}).where({id: workspace.id})
|
||||||
.execute();
|
.execute();
|
||||||
@ -4795,7 +4793,7 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
if (!removedAt) {
|
if (!removedAt) {
|
||||||
docQuery = this._addFeatures(docQuery); // pull in billing information for doc count limits
|
docQuery = this._addFeatures(docQuery); // pull in billing information for doc count limits
|
||||||
}
|
}
|
||||||
const doc: Document = this.unwrapQueryResult(await verifyIsPermitted(docQuery));
|
const doc: Document = this.unwrapQueryResult(await verifyEntity(docQuery));
|
||||||
if (!removedAt) {
|
if (!removedAt) {
|
||||||
await this._checkRoomForAnotherDoc(doc.workspace, manager);
|
await this._checkRoomForAnotherDoc(doc.workspace, manager);
|
||||||
}
|
}
|
||||||
@ -4829,16 +4827,59 @@ export class HomeDBManager extends EventEmitter {
|
|||||||
if (thisUser) { users.push(thisUser); }
|
if (thisUser) { users.push(thisUser); }
|
||||||
return { personal: true, public: !realAccess };
|
return { personal: true, public: !realAccess };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _getWorkspaceWithACLRules(scope: Scope, wsId: number, options: Partial<QueryOptions> = {}) {
|
||||||
|
const query = this._workspace(scope, wsId, {
|
||||||
|
markPermissions: Permissions.VIEW,
|
||||||
|
...options
|
||||||
|
})
|
||||||
|
// Join the workspace's ACL rules (with 1st level groups/users listed).
|
||||||
|
.leftJoinAndSelect('workspaces.aclRules', 'acl_rules')
|
||||||
|
.leftJoinAndSelect('acl_rules.group', 'workspace_groups')
|
||||||
|
.leftJoinAndSelect('workspace_groups.memberUsers', 'workspace_group_users')
|
||||||
|
.leftJoinAndSelect('workspace_groups.memberGroups', 'workspace_group_groups')
|
||||||
|
.leftJoinAndSelect('workspace_group_users.logins', 'workspace_user_logins')
|
||||||
|
.leftJoinAndSelect('workspaces.org', 'org');
|
||||||
|
return verifyEntity(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _buildOrgWithACLRulesQuery(scope: Scope, org: number|string, opts: Partial<QueryOptions> = {}) {
|
||||||
|
return this.org(scope, org, {
|
||||||
|
needRealOrg: true,
|
||||||
|
...opts
|
||||||
|
})
|
||||||
|
// Join the org's ACL rules (with 1st level groups/users listed).
|
||||||
|
.leftJoinAndSelect('orgs.aclRules', 'acl_rules')
|
||||||
|
.leftJoinAndSelect('acl_rules.group', 'org_groups')
|
||||||
|
.leftJoinAndSelect('org_groups.memberUsers', 'org_member_users')
|
||||||
|
.leftJoinAndSelect('org_member_users.logins', 'user_logins');
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getOrgWithACLRules(scope: Scope, org: number|string) {
|
||||||
|
const orgQuery = this._buildOrgWithACLRulesQuery(scope, org, {
|
||||||
|
markPermissions: Permissions.VIEW,
|
||||||
|
allowSpecialPermit: true,
|
||||||
|
});
|
||||||
|
return verifyEntity(orgQuery);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a QueryResult reflecting the output of a query builder.
|
// Return a QueryResult reflecting the output of a query builder.
|
||||||
// Checks on the "is_permitted" field which select queries set on resources to
|
// Checks on the "is_permitted" field which select queries set on resources to
|
||||||
// indicate whether the user has access.
|
// indicate whether the user has access.
|
||||||
|
//
|
||||||
// If the output is empty, we signal that the desired resource does not exist.
|
// If the output is empty, we signal that the desired resource does not exist.
|
||||||
// If the "is_permitted" field is falsy, we signal that the resource is forbidden.
|
//
|
||||||
|
// If we retrieve more than 1 entity, we signal that the request is ambiguous.
|
||||||
|
//
|
||||||
|
// If the "is_permitted" field is falsy, we signal that the resource is forbidden,
|
||||||
|
// unless skipPermissionCheck is set.
|
||||||
|
//
|
||||||
// Returns the resource fetched by the queryBuilder.
|
// Returns the resource fetched by the queryBuilder.
|
||||||
async function verifyIsPermitted(
|
async function verifyEntity(
|
||||||
queryBuilder: SelectQueryBuilder<any>
|
queryBuilder: SelectQueryBuilder<any>,
|
||||||
|
options: { skipPermissionCheck?: boolean } = {}
|
||||||
): Promise<QueryResult<any>> {
|
): Promise<QueryResult<any>> {
|
||||||
const results = await queryBuilder.getRawAndEntities();
|
const results = await queryBuilder.getRawAndEntities();
|
||||||
if (results.entities.length === 0) {
|
if (results.entities.length === 0) {
|
||||||
@ -4851,7 +4892,7 @@ async function verifyIsPermitted(
|
|||||||
status: 400,
|
status: 400,
|
||||||
errMessage: `ambiguous ${getFrom(queryBuilder)} request`
|
errMessage: `ambiguous ${getFrom(queryBuilder)} request`
|
||||||
};
|
};
|
||||||
} else if (!results.raw[0].is_permitted) {
|
} else if (!options.skipPermissionCheck && !results.raw[0].is_permitted) {
|
||||||
return {
|
return {
|
||||||
status: 403,
|
status: 403,
|
||||||
errMessage: "access denied"
|
errMessage: "access denied"
|
||||||
|
@ -257,7 +257,8 @@ _email_regexp = re.compile(
|
|||||||
([A-Za-z0-9] # Each part of hostname must start with alphanumeric
|
([A-Za-z0-9] # Each part of hostname must start with alphanumeric
|
||||||
([A-Za-z0-9-]*[A-Za-z0-9])?\. # May have dashes inside, but end in alphanumeric
|
([A-Za-z0-9-]*[A-Za-z0-9])?\. # May have dashes inside, but end in alphanumeric
|
||||||
)+
|
)+
|
||||||
[A-Za-z]{2,6}$ # Restrict top-level domain to length {2,6}. Google seems
|
[A-Za-z]{2,24}$ # Restrict top-level domain to length {2,24} (theoretically,
|
||||||
|
# the max length is 63 bytes as per RFC 1034). Google seems
|
||||||
# to use a whitelist for TLDs longer than 2 characters.
|
# to use a whitelist for TLDs longer than 2 characters.
|
||||||
""", re.UNICODE | re.VERBOSE)
|
""", re.UNICODE | re.VERBOSE)
|
||||||
|
|
||||||
@ -289,7 +290,8 @@ def ISEMAIL(value):
|
|||||||
>>> ISEMAIL("john@aol...com")
|
>>> ISEMAIL("john@aol...com")
|
||||||
False
|
False
|
||||||
|
|
||||||
More tests:
|
More tests: Google Sheets Grist
|
||||||
|
------------- -----
|
||||||
>>> ISEMAIL("Abc@example.com") # True, True
|
>>> ISEMAIL("Abc@example.com") # True, True
|
||||||
True
|
True
|
||||||
>>> ISEMAIL("Abc.123@example.com") # True, True
|
>>> ISEMAIL("Abc.123@example.com") # True, True
|
||||||
@ -314,6 +316,10 @@ def ISEMAIL(value):
|
|||||||
True
|
True
|
||||||
>>> ISEMAIL("Bob_O'Reilly+tag@example.com") # False, True
|
>>> ISEMAIL("Bob_O'Reilly+tag@example.com") # False, True
|
||||||
True
|
True
|
||||||
|
>>> ISEMAIL("marie@isola.corsica") # False, True
|
||||||
|
True
|
||||||
|
>>> ISEMAIL("fabio@disapproved.solutions") # False, True
|
||||||
|
True
|
||||||
>>> ISEMAIL(u"фыва@mail.ru") # False, True
|
>>> ISEMAIL(u"фыва@mail.ru") # False, True
|
||||||
True
|
True
|
||||||
>>> ISEMAIL("my@baddash.-.com") # True, False
|
>>> ISEMAIL("my@baddash.-.com") # True, False
|
||||||
@ -324,8 +330,6 @@ def ISEMAIL(value):
|
|||||||
False
|
False
|
||||||
>>> ISEMAIL("john@-.com") # True, False
|
>>> ISEMAIL("john@-.com") # True, False
|
||||||
False
|
False
|
||||||
>>> ISEMAIL("fabio@disapproved.solutions") # False, False
|
|
||||||
False
|
|
||||||
>>> ISEMAIL("!def!xyz%abc@example.com") # False, False
|
>>> ISEMAIL("!def!xyz%abc@example.com") # False, False
|
||||||
False
|
False
|
||||||
>>> ISEMAIL("!#$%&'*+-/=?^_`.{|}~@example.com") # False, False
|
>>> ISEMAIL("!#$%&'*+-/=?^_`.{|}~@example.com") # False, False
|
||||||
@ -391,7 +395,8 @@ _url_regexp = re.compile(
|
|||||||
([A-Za-z0-9] # Each part of hostname must start with alphanumeric
|
([A-Za-z0-9] # Each part of hostname must start with alphanumeric
|
||||||
([A-Za-z0-9-]*[A-Za-z0-9])?\. # May have dashes inside, but end in alphanumeric
|
([A-Za-z0-9-]*[A-Za-z0-9])?\. # May have dashes inside, but end in alphanumeric
|
||||||
)+
|
)+
|
||||||
[A-Za-z]{2,6} # Restrict top-level domain to length {2,6}. Google seems
|
[A-Za-z]{2,24} # Restrict top-level domain to length {2,24} (theoretically,
|
||||||
|
# the max length is 63 bytes as per RFC 1034). Google seems
|
||||||
# to use a whitelist for TLDs longer than 2 characters.
|
# to use a whitelist for TLDs longer than 2 characters.
|
||||||
([/?][-\w!#$%&'()*+,./:;=?@~]*)?$ # Notably, this excludes <, >, and ".
|
([/?][-\w!#$%&'()*+,./:;=?@~]*)?$ # Notably, this excludes <, >, and ".
|
||||||
""", re.VERBOSE)
|
""", re.VERBOSE)
|
||||||
@ -437,6 +442,8 @@ def ISURL(value):
|
|||||||
True
|
True
|
||||||
>>> ISURL("http://foo.com/!#$%25&'()*+,-./=?@_~")
|
>>> ISURL("http://foo.com/!#$%25&'()*+,-./=?@_~")
|
||||||
True
|
True
|
||||||
|
>>> ISURL("http://collectivite.isla.corsica")
|
||||||
|
True
|
||||||
>>> ISURL("http://../")
|
>>> ISURL("http://../")
|
||||||
False
|
False
|
||||||
>>> ISURL("http://??/")
|
>>> ISURL("http://??/")
|
||||||
|
@ -16,4 +16,4 @@ if [[ "$GRIST_SANDBOX_FLAVOR" = "gvisor" ]]; then
|
|||||||
./sandbox/gvisor/update_engine_checkpoint.sh
|
./sandbox/gvisor/update_engine_checkpoint.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
NODE_PATH=_build:_build/stubs:_build/ext node _build/stubs/app/server/server.js
|
exec env NODE_PATH=_build:_build/stubs:_build/ext node _build/stubs/app/server/server.js
|
||||||
|
@ -666,7 +666,8 @@
|
|||||||
"Columns_other": "Spalten",
|
"Columns_other": "Spalten",
|
||||||
"Fields_one": "Feld",
|
"Fields_one": "Feld",
|
||||||
"Fields_other": "Felder",
|
"Fields_other": "Felder",
|
||||||
"Add referenced columns": "Referenzspalten hinzufügen"
|
"Add referenced columns": "Referenzspalten hinzufügen",
|
||||||
|
"Reset form": "Formular zurücksetzen"
|
||||||
},
|
},
|
||||||
"RowContextMenu": {
|
"RowContextMenu": {
|
||||||
"Copy anchor link": "Ankerlink kopieren",
|
"Copy anchor link": "Ankerlink kopieren",
|
||||||
@ -743,7 +744,8 @@
|
|||||||
"TOOLS": "WERKZEUGE",
|
"TOOLS": "WERKZEUGE",
|
||||||
"Tour of this Document": "Tour durch dieses Dokument",
|
"Tour of this Document": "Tour durch dieses Dokument",
|
||||||
"Validate Data": "Daten validieren",
|
"Validate Data": "Daten validieren",
|
||||||
"Settings": "Einstellungen"
|
"Settings": "Einstellungen",
|
||||||
|
"API Console": "API-Konsole"
|
||||||
},
|
},
|
||||||
"TopBar": {
|
"TopBar": {
|
||||||
"Manage Team": "Team verwalten"
|
"Manage Team": "Team verwalten"
|
||||||
@ -1319,5 +1321,8 @@
|
|||||||
"Delete card": "Karte löschen",
|
"Delete card": "Karte löschen",
|
||||||
"Copy anchor link": "Ankerlink kopieren",
|
"Copy anchor link": "Ankerlink kopieren",
|
||||||
"Insert card": "Karte einfügen"
|
"Insert card": "Karte einfügen"
|
||||||
|
},
|
||||||
|
"HiddenQuestionConfig": {
|
||||||
|
"Hidden fields": "Ausgeblendete Felder"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -650,7 +650,8 @@
|
|||||||
"Submit another response": "Submit another response",
|
"Submit another response": "Submit another response",
|
||||||
"Submit button label": "Submit button label",
|
"Submit button label": "Submit button label",
|
||||||
"Success text": "Success text",
|
"Success text": "Success text",
|
||||||
"Table column name": "Table column name"
|
"Table column name": "Table column name",
|
||||||
|
"Enter redirect URL": "Enter redirect URL"
|
||||||
},
|
},
|
||||||
"RowContextMenu": {
|
"RowContextMenu": {
|
||||||
"Copy anchor link": "Copy anchor link",
|
"Copy anchor link": "Copy anchor link",
|
||||||
@ -869,7 +870,11 @@
|
|||||||
"You do not have access to this organization's documents.": "You do not have access to this organization's documents.",
|
"You do not have access to this organization's documents.": "You do not have access to this organization's documents.",
|
||||||
"Account deleted{{suffix}}": "Account deleted{{suffix}}",
|
"Account deleted{{suffix}}": "Account deleted{{suffix}}",
|
||||||
"Sign up": "Sign up",
|
"Sign up": "Sign up",
|
||||||
"Your account has been deleted.": "Your account has been deleted."
|
"Your account has been deleted.": "Your account has been deleted.",
|
||||||
|
"An unknown error occurred.": "An unknown error occurred.",
|
||||||
|
"Build your own form": "Build your own form",
|
||||||
|
"Form not found": "Form not found",
|
||||||
|
"Powered by": "Powered by"
|
||||||
},
|
},
|
||||||
"menus": {
|
"menus": {
|
||||||
"* Workspaces are available on team plans. ": "* Workspaces are available on team plans. ",
|
"* Workspaces are available on team plans. ": "* Workspaces are available on team plans. ",
|
||||||
@ -901,7 +906,8 @@
|
|||||||
"Don't show again.": "Don't show again.",
|
"Don't show again.": "Don't show again.",
|
||||||
"Don't show tips": "Don't show tips",
|
"Don't show tips": "Don't show tips",
|
||||||
"Undo to restore": "Undo to restore",
|
"Undo to restore": "Undo to restore",
|
||||||
"Got it": "Got it"
|
"Got it": "Got it",
|
||||||
|
"Don't show again": "Don't show again"
|
||||||
},
|
},
|
||||||
"pages": {
|
"pages": {
|
||||||
"Duplicate Page": "Duplicate Page",
|
"Duplicate Page": "Duplicate Page",
|
||||||
@ -1318,7 +1324,8 @@
|
|||||||
"Paragraph": "Paragraph",
|
"Paragraph": "Paragraph",
|
||||||
"Paste": "Paste",
|
"Paste": "Paste",
|
||||||
"Separator": "Separator",
|
"Separator": "Separator",
|
||||||
"Unmapped fields": "Unmapped fields"
|
"Unmapped fields": "Unmapped fields",
|
||||||
|
"Header": "Header"
|
||||||
},
|
},
|
||||||
"UnmappedFieldsConfig": {
|
"UnmappedFieldsConfig": {
|
||||||
"Clear": "Clear",
|
"Clear": "Clear",
|
||||||
|
@ -666,7 +666,8 @@
|
|||||||
"Columns_other": "Colunas",
|
"Columns_other": "Colunas",
|
||||||
"Fields_one": "Campo",
|
"Fields_one": "Campo",
|
||||||
"Fields_other": "Campos",
|
"Fields_other": "Campos",
|
||||||
"Add referenced columns": "Adicionar colunas referenciadas"
|
"Add referenced columns": "Adicionar colunas referenciadas",
|
||||||
|
"Reset form": "Restaurar formulário"
|
||||||
},
|
},
|
||||||
"RowContextMenu": {
|
"RowContextMenu": {
|
||||||
"Copy anchor link": "Copiar o link de ancoragem",
|
"Copy anchor link": "Copiar o link de ancoragem",
|
||||||
@ -743,7 +744,8 @@
|
|||||||
"TOOLS": "FERRAMENTAS",
|
"TOOLS": "FERRAMENTAS",
|
||||||
"Tour of this Document": "Tour desse Documento",
|
"Tour of this Document": "Tour desse Documento",
|
||||||
"Validate Data": "Validar dados",
|
"Validate Data": "Validar dados",
|
||||||
"Settings": "Configurações"
|
"Settings": "Configurações",
|
||||||
|
"API Console": "Consola API"
|
||||||
},
|
},
|
||||||
"TopBar": {
|
"TopBar": {
|
||||||
"Manage Team": "Gerenciar Equipe"
|
"Manage Team": "Gerenciar Equipe"
|
||||||
@ -1319,5 +1321,8 @@
|
|||||||
"Delete card": "Excluir cartão",
|
"Delete card": "Excluir cartão",
|
||||||
"Copy anchor link": "Copiar link de ancoragem",
|
"Copy anchor link": "Copiar link de ancoragem",
|
||||||
"Insert card": "Inserir cartão"
|
"Insert card": "Inserir cartão"
|
||||||
|
},
|
||||||
|
"HiddenQuestionConfig": {
|
||||||
|
"Hidden fields": "Campos ocultos"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,19 @@
|
|||||||
"Add column": "Dodaj stolpec",
|
"Add column": "Dodaj stolpec",
|
||||||
"Last updated by": "Nazadnje posodobil",
|
"Last updated by": "Nazadnje posodobil",
|
||||||
"Detect duplicates in...": "Zaznaj dvojnike v ...",
|
"Detect duplicates in...": "Zaznaj dvojnike v ...",
|
||||||
"Last updated at": "Nazadnje posodobljeno ob"
|
"Last updated at": "Nazadnje posodobljeno ob",
|
||||||
|
"Choice": "Izbira",
|
||||||
|
"Choice List": "Izbirni seznam",
|
||||||
|
"Reference": "Referenca",
|
||||||
|
"Reference List": "Referenčni seznam",
|
||||||
|
"Attachment": "Priponka",
|
||||||
|
"Any": "Karkoli",
|
||||||
|
"Toggle": "Preklopi",
|
||||||
|
"Date": "Datum",
|
||||||
|
"Numeric": "Numeričen",
|
||||||
|
"Text": "Tekst",
|
||||||
|
"Integer": "celo število",
|
||||||
|
"DateTime": "Datum čas"
|
||||||
},
|
},
|
||||||
"HomeLeftPane": {
|
"HomeLeftPane": {
|
||||||
"Trash": "Koš",
|
"Trash": "Koš",
|
||||||
@ -330,7 +342,8 @@
|
|||||||
"Add to page": "Dodaj na stran",
|
"Add to page": "Dodaj na stran",
|
||||||
"Show raw data": "Prikaži neobdelane podatke",
|
"Show raw data": "Prikaži neobdelane podatke",
|
||||||
"Copy anchor link": "Kopiraj sidrno povezavo",
|
"Copy anchor link": "Kopiraj sidrno povezavo",
|
||||||
"Collapse widget": "Strni gradnik"
|
"Collapse widget": "Strni gradnik",
|
||||||
|
"Create a form": "Ustvari obrazec"
|
||||||
},
|
},
|
||||||
"FieldEditor": {
|
"FieldEditor": {
|
||||||
"Unable to finish saving edited cell": "Ni mogoče dokončati shranjevanja urejene celice",
|
"Unable to finish saving edited cell": "Ni mogoče dokončati shranjevanja urejene celice",
|
||||||
@ -580,7 +593,24 @@
|
|||||||
"SELECTOR FOR": "SELEKTOR ZA",
|
"SELECTOR FOR": "SELEKTOR ZA",
|
||||||
"Sort & Filter": "Razvrščanje in filtriranje",
|
"Sort & Filter": "Razvrščanje in filtriranje",
|
||||||
"Widget": "Pripomoček",
|
"Widget": "Pripomoček",
|
||||||
"Reset form": "Ponastavi obrazec"
|
"Reset form": "Ponastavi obrazec",
|
||||||
|
"Default field value": "Privzeta vrednost polja",
|
||||||
|
"Display button": "Gumb za prikaz",
|
||||||
|
"Field rules": "Pravila polja",
|
||||||
|
"Field title": "Naziv polja",
|
||||||
|
"Hidden field": "Skrito polje",
|
||||||
|
"Layout": "Postavitev",
|
||||||
|
"Redirection": "Preusmeritev",
|
||||||
|
"Required field": "Obvezno polje",
|
||||||
|
"Submit another response": "Predložite drug odgovor",
|
||||||
|
"Submission": "Predložitev",
|
||||||
|
"Success text": "Uspešno besedilo",
|
||||||
|
"Table column name": "Ime stolpca tabele",
|
||||||
|
"Configuration": "Konfiguracija",
|
||||||
|
"Enter text": "Vnesi besedilo",
|
||||||
|
"Redirect automatically after submission": "Po oddaji samodejno preusmeri",
|
||||||
|
"Enter redirect URL": "Vnesi preusmeritveni URL",
|
||||||
|
"Submit button label": "Oznaka gumba za pošiljanje"
|
||||||
},
|
},
|
||||||
"FloatingPopup": {
|
"FloatingPopup": {
|
||||||
"Maximize": "Povečajte",
|
"Maximize": "Povečajte",
|
||||||
@ -1060,7 +1090,11 @@
|
|||||||
"Contact support": "Obrnite se na podporo",
|
"Contact support": "Obrnite se na podporo",
|
||||||
"Account deleted{{suffix}}": "Račun je izbrisan{{suffix}}",
|
"Account deleted{{suffix}}": "Račun je izbrisan{{suffix}}",
|
||||||
"Your account has been deleted.": "Vaš račun je bil izbrisan.",
|
"Your account has been deleted.": "Vaš račun je bil izbrisan.",
|
||||||
"Sign up": "Prijava"
|
"Sign up": "Prijava",
|
||||||
|
"Build your own form": "Ustvari svoj obrazec",
|
||||||
|
"Powered by": "Poganja ga",
|
||||||
|
"An unknown error occurred.": "Prišlo je do neznane napake.",
|
||||||
|
"Form not found": "Ne najdem obrazca"
|
||||||
},
|
},
|
||||||
"WidgetTitle": {
|
"WidgetTitle": {
|
||||||
"DATA TABLE NAME": "IME PODATKOVNE TABELE",
|
"DATA TABLE NAME": "IME PODATKOVNE TABELE",
|
||||||
@ -1245,7 +1279,17 @@
|
|||||||
"modals": {
|
"modals": {
|
||||||
"Save": "Shrani",
|
"Save": "Shrani",
|
||||||
"Cancel": "Prekliči",
|
"Cancel": "Prekliči",
|
||||||
"Ok": "v redu"
|
"Ok": "v redu",
|
||||||
|
"Are you sure you want to delete this record?": "Ali si prepričan, da želiš izbrisati ta zapis?",
|
||||||
|
"Dismiss": "Opusti",
|
||||||
|
"Don't ask again.": "Ne sprašuj več.",
|
||||||
|
"Don't show again.": "Ne pokaži več.",
|
||||||
|
"Don't show tips": "Ne pokaži nasvetov",
|
||||||
|
"Undo to restore": "Razveljavi obnovitev",
|
||||||
|
"Got it": "Razumem",
|
||||||
|
"Don't show again": "Ne pokaži več",
|
||||||
|
"Are you sure you want to delete these records?": "Ali si prepričan, da želiš izbrisati te zapise?",
|
||||||
|
"Delete": "Briši"
|
||||||
},
|
},
|
||||||
"sendToDrive": {
|
"sendToDrive": {
|
||||||
"Sending file to Google Drive": "Pošiljanje datoteke v Google Drive"
|
"Sending file to Google Drive": "Pošiljanje datoteke v Google Drive"
|
||||||
@ -1260,5 +1304,35 @@
|
|||||||
},
|
},
|
||||||
"HiddenQuestionConfig": {
|
"HiddenQuestionConfig": {
|
||||||
"Hidden fields": "Skrita polja"
|
"Hidden fields": "Skrita polja"
|
||||||
|
},
|
||||||
|
"FormView": {
|
||||||
|
"Publish": "Objavi",
|
||||||
|
"Unpublish your form?": "Želiš preklicati objavo obrazca?",
|
||||||
|
"Publish your form?": "Želiš objaviti obrazec?",
|
||||||
|
"Unpublish": "Prekliči objavo"
|
||||||
|
},
|
||||||
|
"Menu": {
|
||||||
|
"Building blocks": "Gradniki",
|
||||||
|
"Columns": "Stolpci",
|
||||||
|
"Copy": "Kopiraj",
|
||||||
|
"Cut": "Izreži",
|
||||||
|
"Insert question above": "Vstavi vprašanje zgoraj",
|
||||||
|
"Paste": "Prilepi",
|
||||||
|
"Separator": "Ločilo",
|
||||||
|
"Unmapped fields": "Nepreslikana polja",
|
||||||
|
"Header": "Glava",
|
||||||
|
"Insert question below": "Vstavi vprašanje spodaj",
|
||||||
|
"Paragraph": "Odstavek"
|
||||||
|
},
|
||||||
|
"UnmappedFieldsConfig": {
|
||||||
|
"Clear": "Očisti",
|
||||||
|
"Mapped": "Preslikano",
|
||||||
|
"Select All": "Izberi vse",
|
||||||
|
"Map fields": "Preslikaj polja",
|
||||||
|
"Unmap fields": "Odstrani preslikavo polj",
|
||||||
|
"Unmapped": "Nepreslikan"
|
||||||
|
},
|
||||||
|
"Editor": {
|
||||||
|
"Delete": "Briši"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,6 +247,7 @@ describe('gutil', function() {
|
|||||||
assert.isTrue(gutil.isEmail('email@subdomain.do-main.com'));
|
assert.isTrue(gutil.isEmail('email@subdomain.do-main.com'));
|
||||||
assert.isTrue(gutil.isEmail('firstname+lastname@domain.com'));
|
assert.isTrue(gutil.isEmail('firstname+lastname@domain.com'));
|
||||||
assert.isTrue(gutil.isEmail('email@domain.co.jp'));
|
assert.isTrue(gutil.isEmail('email@domain.co.jp'));
|
||||||
|
assert.isTrue(gutil.isEmail('marie@isola.corsica'));
|
||||||
|
|
||||||
assert.isFalse(gutil.isEmail('plainaddress'));
|
assert.isFalse(gutil.isEmail('plainaddress'));
|
||||||
assert.isFalse(gutil.isEmail('@domain.com'));
|
assert.isFalse(gutil.isEmail('@domain.com'));
|
||||||
|
@ -924,6 +924,21 @@ describe('ApiServerAccess', function() {
|
|||||||
isMember: true,
|
isMember: true,
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const deltaOrg = {
|
||||||
|
users: {
|
||||||
|
[kiwiEmail]: "owners",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const respDeltaOrg = await axios.patch(`${homeUrl}/api/orgs/${oid}/access`, {delta: deltaOrg}, chimpy);
|
||||||
|
assert.equal(respDeltaOrg.status, 200);
|
||||||
|
|
||||||
|
const resp3 = await axios.get(`${homeUrl}/api/workspaces/${wid}/access`, chimpy);
|
||||||
|
assert.include(resp3.data.users.find((user: any) => user.email === kiwiEmail), {
|
||||||
|
access: "editors",
|
||||||
|
parentAccess: "owners"
|
||||||
|
});
|
||||||
|
|
||||||
// Reset the access settings
|
// Reset the access settings
|
||||||
const resetDelta = {
|
const resetDelta = {
|
||||||
maxInheritedRole: "owners",
|
maxInheritedRole: "owners",
|
||||||
@ -933,6 +948,13 @@ describe('ApiServerAccess', function() {
|
|||||||
};
|
};
|
||||||
const resetResp = await axios.patch(`${homeUrl}/api/workspaces/${wid}/access`, {delta: resetDelta}, chimpy);
|
const resetResp = await axios.patch(`${homeUrl}/api/workspaces/${wid}/access`, {delta: resetDelta}, chimpy);
|
||||||
assert.equal(resetResp.status, 200);
|
assert.equal(resetResp.status, 200);
|
||||||
|
const resetOrgDelta = {
|
||||||
|
users: {
|
||||||
|
[kiwiEmail]: "members",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const resetOrgResp = await axios.patch(`${homeUrl}/api/orgs/${oid}/access`, {delta: resetOrgDelta}, chimpy);
|
||||||
|
assert.equal(resetOrgResp.status, 200);
|
||||||
|
|
||||||
// Assert that ws guests are properly displayed.
|
// Assert that ws guests are properly displayed.
|
||||||
// Tests a minor bug that showed ws guests as having null access.
|
// Tests a minor bug that showed ws guests as having null access.
|
||||||
|
@ -22,6 +22,7 @@ import * as path from 'path';
|
|||||||
import {createInitialDb, removeConnection, setUpDB} from 'test/gen-server/seed';
|
import {createInitialDb, removeConnection, setUpDB} from 'test/gen-server/seed';
|
||||||
import {setPlan} from 'test/gen-server/testUtils';
|
import {setPlan} from 'test/gen-server/testUtils';
|
||||||
import {fixturesRoot} from 'test/server/testUtils';
|
import {fixturesRoot} from 'test/server/testUtils';
|
||||||
|
import {isAffirmative} from 'app/common/gutil';
|
||||||
|
|
||||||
export class TestServer {
|
export class TestServer {
|
||||||
public serverUrl: string;
|
public serverUrl: string;
|
||||||
@ -36,7 +37,7 @@ export class TestServer {
|
|||||||
public async start(servers: ServerType[] = ["home"],
|
public async start(servers: ServerType[] = ["home"],
|
||||||
options: FlexServerOptions = {}): Promise<string> {
|
options: FlexServerOptions = {}): Promise<string> {
|
||||||
await createInitialDb();
|
await createInitialDb();
|
||||||
this.server = await mergedServerMain(0, servers, {logToConsole: false,
|
this.server = await mergedServerMain(0, servers, {logToConsole: isAffirmative(process.env.DEBUG),
|
||||||
externalStorage: false,
|
externalStorage: false,
|
||||||
...options});
|
...options});
|
||||||
this.serverUrl = this.server.getOwnUrl();
|
this.serverUrl = this.server.getOwnUrl();
|
||||||
|
@ -5,6 +5,7 @@ import { setupTestSuite } from 'test/nbrowser/testUtils';
|
|||||||
|
|
||||||
describe('CellColor', function() {
|
describe('CellColor', function() {
|
||||||
this.timeout(20000);
|
this.timeout(20000);
|
||||||
|
gu.bigScreen();
|
||||||
const cleanup = setupTestSuite();
|
const cleanup = setupTestSuite();
|
||||||
let doc: string;
|
let doc: string;
|
||||||
|
|
||||||
@ -534,6 +535,9 @@ describe('CellColor', function() {
|
|||||||
await gu.waitForServer();
|
await gu.waitForServer();
|
||||||
await gu.setType(/Toggle/);
|
await gu.setType(/Toggle/);
|
||||||
|
|
||||||
|
// make sure the view pane is scrolled all the way left
|
||||||
|
await gu.sendKeys(Key.ARROW_LEFT);
|
||||||
|
|
||||||
// enter 'true'
|
// enter 'true'
|
||||||
await gu.getCell('E', 1).click();
|
await gu.getCell('E', 1).click();
|
||||||
await gu.enterCell('true');
|
await gu.enterCell('true');
|
||||||
|
@ -22,6 +22,7 @@ async function setCustomWidget() {
|
|||||||
|
|
||||||
describe('CustomView', function() {
|
describe('CustomView', function() {
|
||||||
this.timeout(20000);
|
this.timeout(20000);
|
||||||
|
gu.bigScreen();
|
||||||
const cleanup = setupTestSuite();
|
const cleanup = setupTestSuite();
|
||||||
|
|
||||||
let serving: Serving;
|
let serving: Serving;
|
||||||
|
@ -83,8 +83,9 @@ describe('GridViewNewColumnMenu', function () {
|
|||||||
it('should create a new column', async function () {
|
it('should create a new column', async function () {
|
||||||
await clickAddColumn();
|
await clickAddColumn();
|
||||||
await driver.findWait('.test-new-columns-menu-add-new', 100).click();
|
await driver.findWait('.test-new-columns-menu-add-new', 100).click();
|
||||||
|
await gu.waitForServer();
|
||||||
//discard rename menu
|
//discard rename menu
|
||||||
await driver.findWait('.test-column-title-close', 100).click();
|
await driver.find('.test-column-title-close').click();
|
||||||
//check if new column is present
|
//check if new column is present
|
||||||
const columns = await gu.getColumnNames();
|
const columns = await gu.getColumnNames();
|
||||||
assert.include(columns, 'D', 'new column is not present');
|
assert.include(columns, 'D', 'new column is not present');
|
||||||
@ -208,10 +209,10 @@ describe('GridViewNewColumnMenu', function () {
|
|||||||
`${option.type} option is not present`);
|
`${option.type} option is not present`);
|
||||||
// click on the option and check if column is added with a proper type
|
// click on the option and check if column is added with a proper type
|
||||||
await element.click();
|
await element.click();
|
||||||
|
await gu.waitForServer();
|
||||||
//discard rename menu
|
//discard rename menu
|
||||||
await driver.findWait('.test-column-title-close', 100).click();
|
await driver.findWait('.test-column-title-close', 100).click();
|
||||||
//check if new column is present
|
//check if new column is present
|
||||||
await gu.waitForServer();
|
|
||||||
await gu.selectColumn('D');
|
await gu.selectColumn('D');
|
||||||
await gu.openColumnPanel();
|
await gu.openColumnPanel();
|
||||||
const type = await gu.getType();
|
const type = await gu.getType();
|
||||||
|
@ -294,6 +294,10 @@ describe('ReferenceColumns', function() {
|
|||||||
assert.equal(await driver.find('.celleditor_text_editor').value(), 'da');
|
assert.equal(await driver.find('.celleditor_text_editor').value(), 'da');
|
||||||
assert.equal(await driver.find('.test-ref-editor-item.selected').isPresent(), false);
|
assert.equal(await driver.find('.test-ref-editor-item.selected').isPresent(), false);
|
||||||
|
|
||||||
|
// Clear the typed-in text temporarily. Something changed in a recent version of Chrome,
|
||||||
|
// causing the wrong item to be moused over below when the "Add New" option is visible.
|
||||||
|
await driver.sendKeys(Key.BACK_SPACE, Key.BACK_SPACE);
|
||||||
|
|
||||||
// Mouse over an item.
|
// Mouse over an item.
|
||||||
await driver.findContent('.test-ref-editor-item', /Dark Gray/).mouseMove();
|
await driver.findContent('.test-ref-editor-item', /Dark Gray/).mouseMove();
|
||||||
assert.equal(await driver.find('.celleditor_text_editor').value(), 'Dark Gray');
|
assert.equal(await driver.find('.celleditor_text_editor').value(), 'Dark Gray');
|
||||||
@ -301,10 +305,11 @@ describe('ReferenceColumns', function() {
|
|||||||
|
|
||||||
// Mouse back out of the dropdown
|
// Mouse back out of the dropdown
|
||||||
await driver.find('.celleditor_text_editor').mouseMove();
|
await driver.find('.celleditor_text_editor').mouseMove();
|
||||||
assert.equal(await driver.find('.celleditor_text_editor').value(), 'da');
|
assert.equal(await driver.find('.celleditor_text_editor').value(), '');
|
||||||
assert.equal(await driver.find('.test-ref-editor-item.selected').isPresent(), false);
|
assert.equal(await driver.find('.test-ref-editor-item.selected').isPresent(), false);
|
||||||
|
|
||||||
// Click away to save the typed-in text.
|
// Re-enter the typed-in text and click away to save it.
|
||||||
|
await driver.sendKeys('da', Key.UP);
|
||||||
await gu.getCell({section: 'References', col: 'Color', rowNum: 1}).doClick();
|
await gu.getCell({section: 'References', col: 'Color', rowNum: 1}).doClick();
|
||||||
await gu.waitForServer();
|
await gu.waitForServer();
|
||||||
assert.equal(await cell.getText(), "da");
|
assert.equal(await cell.getText(), "da");
|
||||||
|
@ -582,6 +582,10 @@ describe('ReferenceList', function() {
|
|||||||
assert.equal(await driver.find('.cell_editor .test-tokenfield .test-tokenfield-input').value(), 'da');
|
assert.equal(await driver.find('.cell_editor .test-tokenfield .test-tokenfield-input').value(), 'da');
|
||||||
assert.equal(await driver.find('.test-ref-editor-item.selected').isPresent(), false);
|
assert.equal(await driver.find('.test-ref-editor-item.selected').isPresent(), false);
|
||||||
|
|
||||||
|
// Clear the typed-in text temporarily. Something changed in a recent version of Chrome,
|
||||||
|
// causing the wrong item to be moused over below when the "Add New" option is visible.
|
||||||
|
await driver.sendKeys(Key.BACK_SPACE, Key.BACK_SPACE);
|
||||||
|
|
||||||
// Mouse over an item.
|
// Mouse over an item.
|
||||||
await driver.findContent('.test-ref-editor-item', /Dark Gray/).mouseMove();
|
await driver.findContent('.test-ref-editor-item', /Dark Gray/).mouseMove();
|
||||||
assert.equal(await driver.find('.cell_editor .test-tokenfield .test-tokenfield-input').value(), 'Dark Gray');
|
assert.equal(await driver.find('.cell_editor .test-tokenfield .test-tokenfield-input').value(), 'Dark Gray');
|
||||||
@ -589,10 +593,12 @@ describe('ReferenceList', function() {
|
|||||||
|
|
||||||
// Mouse back out of the dropdown
|
// Mouse back out of the dropdown
|
||||||
await driver.find('.cell_editor .test-tokenfield .test-tokenfield-input').mouseMove();
|
await driver.find('.cell_editor .test-tokenfield .test-tokenfield-input').mouseMove();
|
||||||
assert.equal(await driver.find('.cell_editor .test-tokenfield .test-tokenfield-input').value(), 'da');
|
assert.equal(await driver.find('.cell_editor .test-tokenfield .test-tokenfield-input').value(), '');
|
||||||
assert.equal(await driver.find('.test-ref-editor-item.selected').isPresent(), false);
|
assert.equal(await driver.find('.test-ref-editor-item.selected').isPresent(), false);
|
||||||
|
|
||||||
// Click away and check the cell is now empty since no reference items were added.
|
// Re-enter the typed-in text and click away. Check the cell is now empty since
|
||||||
|
// no reference items were added.
|
||||||
|
await driver.sendKeys('da', Key.UP);
|
||||||
await gu.getCell({section: 'References', col: 'Colors', rowNum: 1}).doClick();
|
await gu.getCell({section: 'References', col: 'Colors', rowNum: 1}).doClick();
|
||||||
await gu.waitForServer();
|
await gu.waitForServer();
|
||||||
assert.equal(await cell.getText(), "");
|
assert.equal(await cell.getText(), "");
|
||||||
|
Loading…
Reference in New Issue
Block a user