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 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 30 additions & 0 deletions jsapp/js/components/permissions/permConstants.ts
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'
);
44 changes: 3 additions & 41 deletions jsapp/js/components/permissions/userAssetPermsEditor.component.tsx
Expand Up @@ -25,12 +25,9 @@ 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';
import {getPartialByUsersListName, getCheckboxLabel} from './utils';

const PARTIAL_PLACEHOLDER = t('Enter usernames separated by comma');
const USERNAMES_SEPARATOR = ',';
Expand Down Expand Up @@ -400,41 +397,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 +513,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={getCheckboxLabel(checkboxName)}
/>
);
}
Expand Down
70 changes: 8 additions & 62 deletions jsapp/js/components/permissions/userPermissionRow.component.tsx
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
115 changes: 114 additions & 1 deletion jsapp/js/components/permissions/utils.ts
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,111 @@ export function getCheckboxNameByPermission(
}
return found;
}

/**
* A wrapper function for getting item from `CHECKBOX_LABELS`. If anything goes
* awry, we will return checkbox name as fallback.
*/
export function getCheckboxLabel(checkboxName: CheckboxNameAll) {
if (checkboxName in CHECKBOX_LABELS) {
return CHECKBOX_LABELS[checkboxName];
}
return checkboxName;
}
magicznyleszek marked this conversation as resolved.
Show resolved Hide resolved

/** 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 && checkboxName in CHECKBOX_LABELS) {
return getCheckboxLabel(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
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