Skip to content

Commit

Permalink
Merge pull request #4734 from kobotoolbox/hardcode-permissions-labels
Browse files Browse the repository at this point in the history
Hardcode permissions labels
  • Loading branch information
magicznyleszek authored Jan 31, 2024
2 parents a31ca29 + ed18734 commit cc21ead
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 103 deletions.
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

0 comments on commit cc21ead

Please sign in to comment.