Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hardcode permissions labels #4734

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions jsapp/js/components/permissions/permConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,33 @@ export const PARTIAL_IMPLIED_CHECKBOX_PAIRS = {
[CHECKBOX_NAMES.submissionsEditPartialByUsers]: CHECKBOX_NAMES.submissionsAdd,
};
Object.freeze(PARTIAL_IMPLIED_CHECKBOX_PAIRS);

/**
* Most of these labels are also available from `api/v2/assets/<uid>/` endpoint
* in the `assignable_permissions` property. Unfortunately due to how the data
* is architectured, the labels for partial permissions are not going to be
* available for multiple types.
*/
export const CHECKBOX_LABELS: {[key in CheckboxNameAll]: string} = {
formView: t('View form'),
formEdit: t('Edit form'),
formManage: t('Manage project'),
submissionsAdd: t('Add submissions'),
submissionsView: t('View submissions'),
submissionsViewPartialByUsers: t('View submissions only from specific users'),
submissionsEdit: t('Edit submissions'),
submissionsEditPartialByUsers: t('Edit submissions only from specific users'),
submissionsValidate: t('Validate submissions'),
submissionsValidatePartialByUsers: t(
'Validate submissions only from specific users'
),
submissionsDelete: t('Delete submissions'),
submissionsDeletePartialByUsers: t(
'Delete submissions only from specific users'
),
};
Object.freeze(CHECKBOX_LABELS);

export const PARTIAL_BY_USERS_DEFAULT_LABEL = t(
'Act on submissions only from specific users'
);
43 changes: 3 additions & 40 deletions jsapp/js/components/permissions/userAssetPermsEditor.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
CHECKBOX_NAMES,
CHECKBOX_PERM_PAIRS,
PARTIAL_IMPLIED_CHECKBOX_PAIRS,
CHECKBOX_LABELS,
} from './permConstants';
import type {
CheckboxNameAll,
Expand All @@ -25,10 +26,7 @@ import type {
PermissionCodename,
} from './permConstants';
import type {AssignablePermsMap} from './sharingForm.component';
import type {
PermissionBase,
AssignablePermissionPartialLabel,
} from 'js/dataInterface';
import type {PermissionBase} from 'js/dataInterface';
import userExistence from 'js/users/userExistence.store';
import {getPartialByUsersListName} from './utils';

Expand Down Expand Up @@ -400,41 +398,6 @@ export default class UserAssetPermsEditor extends React.Component<
return found;
}

getCheckboxLabel(checkboxName: CheckboxNameAll) {
// We need both of these pieces of data, and most probably both of them
// should be available. But because of types we need to be extra safe. If
// anything goes awry, we will return checkbox name as fallback.
const permDef = permConfig.getPermissionByCodename(
CHECKBOX_PERM_PAIRS[checkboxName]
);
if (!permDef) {
return checkboxName;
}
const assignablePerm = this.props.assignablePerms.get(permDef.url);
if (!assignablePerm) {
return checkboxName;
}

// For partial permission we need to dig deeper
if (checkboxName in PARTIAL_PERM_PAIRS) {
// We need to get regular (non partial) permission name that matches
// the partial permission. This is because each partial permissions is
// being stored as `partial_submissions` first, and the actual respective
// submission second.
const permName = PARTIAL_PERM_PAIRS[checkboxName as CheckboxNamePartialByUsers];
if (typeof assignablePerm !== 'string' && permName in assignablePerm) {
return (
assignablePerm[permName as keyof AssignablePermissionPartialLabel] ||
checkboxName
);
}
return checkboxName;
} else {
// We cast it as string, because it is definitely not partial checkbox
return assignablePerm as string;
}
}

isAssignable(permCodename: PermissionCodename) {
const permDef = permConfig.getPermissionByCodename(permCodename);
if (!permDef) {
Expand Down Expand Up @@ -551,7 +514,7 @@ export default class UserAssetPermsEditor extends React.Component<
checked={this.state[checkboxName]}
disabled={isDisabled}
onChange={this.onCheckboxChange.bind(this, checkboxName)}
label={this.getCheckboxLabel(checkboxName)}
label={CHECKBOX_LABELS[checkboxName]}
/>
);
}
Expand Down
70 changes: 8 additions & 62 deletions jsapp/js/components/permissions/userPermissionRow.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import permConfig from './permConfig';
import type {UserPerm} from './permParser';
import type {PermissionBase} from 'js/dataInterface';
import type {AssignablePermsMap} from './sharingForm.component';
import {getPermLabel, getFriendlyPermName} from './utils';

interface UserPermissionRowProps {
assetUid: string;
Expand Down Expand Up @@ -108,72 +109,17 @@ export default class UserPermissionRow extends React.Component<
this.setState({isEditFormVisible: !this.state.isEditFormVisible});
}

// TODO: This doesn't display `partial_permissions` in a nice way, as it
// assumes that there can be only "view" in them, but this is partially
// backend's fault for giving a non universal label to "partial_permissions".
// See: https://github.com/kobotoolbox/kpi/issues/4641
/**
* Note that this renders partial permission using a general label with a list
* of related conditions.
*/
renderPermissions(permissions: UserPerm[]) {
const maxParentheticalUsernames = 3;
return (
<bem.UserRow__perms>
{permissions.map((perm) => {
let permUsers: string[] = [];

if (perm.partial_permissions) {
perm.partial_permissions.forEach((partial) => {
partial.filters.forEach((filter) => {
if (filter._submitted_by) {
permUsers = permUsers.concat(filter._submitted_by.$in);
}
});
});
}

// Keep only unique values
permUsers = [...new Set(permUsers)];

// We fallback to "???" so it's clear when some error happens
let permLabel: string = '???';
if (this.props.assignablePerms.has(perm.permission)) {
const assignablePerm = this.props.assignablePerms.get(
perm.permission
);
if (typeof assignablePerm === 'object') {
// let's assume back end always returns a `default` property with
// nested permissions
permLabel = assignablePerm.default;
} else if (assignablePerm) {
permLabel = assignablePerm;
}
}

// Hopefully this is friendly to translators of RTL languages
let permNameTemplate;
if (permUsers.length === 0) {
permNameTemplate = '##permission_label##';
} else if (permUsers.length <= maxParentheticalUsernames) {
permNameTemplate = t('##permission_label## (##username_list##)');
} else if (permUsers.length === maxParentheticalUsernames + 1) {
permNameTemplate = t(
'##permission_label## (##username_list## and 1 other)'
);
} else {
permNameTemplate = t(
'##permission_label## (##username_list## and ' +
'##hidden_username_count## others)'
);
}

const friendlyPermName = permNameTemplate
.replace('##permission_label##', permLabel)
.replace(
'##username_list##',
permUsers.slice(0, maxParentheticalUsernames).join(', ')
)
.replace(
'##hidden_username_count##',
String(permUsers.length - maxParentheticalUsernames)
);
const permLabel = getPermLabel(perm);

const friendlyPermName = getFriendlyPermName(perm);

return (
<bem.UserRow__perm key={permLabel}>
Expand Down
104 changes: 103 additions & 1 deletion jsapp/js/components/permissions/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import type {
CheckboxNamePartialByUsers,
PartialByUsersListName,
} from './permConstants';
import {CHECKBOX_PERM_PAIRS} from './permConstants';
import {
CHECKBOX_PERM_PAIRS,
CHECKBOX_LABELS,
PARTIAL_BY_USERS_DEFAULT_LABEL,
} from './permConstants';
import type {UserPerm} from './permParser';

/** For `.find`-ing the permissions */
function _doesPermMatch(
Expand Down Expand Up @@ -250,3 +255,100 @@ export function getCheckboxNameByPermission(
}
return found;
}

/** Detects if partial permissions is of "by users" kind */
export function isPartialByUsers(perm: UserPerm) {
// TODO for now this only checks if this is partial permission, as there is
// only one type (more to come). In future this would need some smart way
// to recognize what Django filter is being used in the `partial_permissions`
// object.
return (
'partial_permissions' in perm && perm.partial_permissions !== undefined
);
}

/**
* Returns a human readable permission label, has to do some juggling for
* partial permissions. Fallback is permission codename.
*/
export function getPermLabel(perm: UserPerm) {
// For partial permissions we return a general label that matches all possible
// partial permissions (i.e. same label for "View submissions only from
// specific users" and "Edit submissions only from specific users" etc.)
if (isPartialByUsers(perm)) {
return PARTIAL_BY_USERS_DEFAULT_LABEL;
}

// Get permission definition
const permDef = permConfig.getPermission(perm.permission);

if (permDef) {
const checkboxName = getCheckboxNameByPermission(permDef.codename);

if (checkboxName) {
return CHECKBOX_LABELS[checkboxName];
}
}

// If we couldn't get the definition, we will display "???", so it's clear
// something is terribly wrong. But this case is ~impossible to get, and we
// mostly have it for TS reasons.
return '???';
}

/**
* Displays a user friendly name of given permission. For partial permissions it
* will include the list of users (limited by `maxParentheticalUsernames`) in
* the name.
*/
export function getFriendlyPermName(
perm: UserPerm,
maxParentheticalUsernames = 3
) {
const permLabel = getPermLabel(perm);

let permUsers: string[] = [];

if (perm.partial_permissions) {
perm.partial_permissions.forEach((partial) => {
partial.filters.forEach((filter) => {
if (filter._submitted_by) {
permUsers = permUsers.concat(filter._submitted_by.$in);
}
});
});
}

// Keep only unique values
permUsers = [...new Set(permUsers)];

// Hopefully this is friendly to translators of RTL languages
let permNameTemplate;
if (permUsers.length === 0) {
permNameTemplate = '##permission_label##';
} else if (permUsers.length <= maxParentheticalUsernames) {
permNameTemplate = t('##permission_label## (##username_list##)');
} else if (permUsers.length === maxParentheticalUsernames + 1) {
permNameTemplate = t(
'##permission_label## (##username_list## and 1 other)'
);
} else {
permNameTemplate = t(
'##permission_label## (##username_list## and ' +
'##hidden_username_count## others)'
);
}

const friendlyPermName = permNameTemplate
.replace('##permission_label##', permLabel)
.replace(
'##username_list##',
permUsers.slice(0, maxParentheticalUsernames).join(', ')
)
.replace(
'##hidden_username_count##',
String(permUsers.length - maxParentheticalUsernames)
);

return friendlyPermName;
}
7 changes: 7 additions & 0 deletions jsapp/js/dataInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,13 @@ interface AssignablePermissionRegular {
label: string;
}

/**
* A list of labels for partial permissions.
*
* WARNING: it only includes labels for `…PartialByUsers` type ("…only from
* specific users"), so please use `CHECKBOX_LABELS` from `permConstants` file
* instead.
*/
export interface AssignablePermissionPartialLabel {
default: string;
view_submissions: string;
Expand Down
Loading