Skip to content

Commit

Permalink
Security UI Fixes (#233)
Browse files Browse the repository at this point in the history
* Security UI Fixes

---------

Co-authored-by: Curtis Upshall <[email protected]>
  • Loading branch information
jeznorth and curtisupshall authored Jan 22, 2024
1 parent 9084dec commit 9ef67c6
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 185 deletions.
8 changes: 2 additions & 6 deletions app/src/components/security/SecuritiesDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,7 @@ const SecuritiesDialog = (props: ISecuritiesDialogProps) => {
dialogContext.setSnackbar({
snackbarMessage: (
<Typography variant="body2" component="div">
{ApplySecurityRulesI18N.applySecuritySuccess(
patch.stagedForApply.length,
patch.stagedForRemove.length,
props.submissionFeatureIds.length
)}
{ApplySecurityRulesI18N.applySecuritySuccess(props.submissionFeatureIds.length)}
</Typography>
),
open: true
Expand All @@ -61,7 +57,7 @@ const SecuritiesDialog = (props: ISecuritiesDialogProps) => {
return (
<EditDialog<IPatchFeatureSecurityRules>
isLoading={isLoading}
dialogTitle={hasSecurity ? 'Edit Security Reasons' : ' Add Security Reasons'}
dialogTitle={hasSecurity ? 'Edit Security' : ' Add Security'}
open={props.open}
dialogSaveButtonLabel="APPLY"
onCancel={props.onClose}
Expand Down
13 changes: 10 additions & 3 deletions app/src/components/security/SecurityRuleActionCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ const SecurityRuleActionCard = (props: ISecurityRuleActionCardProps) => {
variant="outlined"
sx={{
display: 'flex',
px: 2,
py: 1.5,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
p: 2,
background: grey[100]
}}>
<SecurityRuleCard {...rest} />
Expand All @@ -30,7 +29,15 @@ const SecurityRuleActionCard = (props: ISecurityRuleActionCardProps) => {
<Icon path={mdiClose} size={1} />
</IconButton>
) : (
<Button variant={props.action === 'remove' ? 'contained' : 'outlined'} color="error" onClick={() => onRemove()}>
<Button
variant={props.action === 'remove' ? 'contained' : 'outlined'}
color="error"
sx={{
width: '6rem',
fontWeight: 700,
letterSpacing: '0.02rem'
}}
onClick={() => onRemove()}>
Remove
</Button>
)}
Expand Down
73 changes: 40 additions & 33 deletions app/src/components/security/SecurityRuleCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Box, Typography } from '@mui/material';
import { Stack } from '@mui/system';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';

export interface ISecurityRuleCardProps {
key?: string | number;
Expand All @@ -11,46 +11,53 @@ export interface ISecurityRuleCardProps {

const SecurityRuleCard = (props: ISecurityRuleCardProps) => {
return (
<Box>
<Typography variant="body2" color="textSecondary">
<Stack gap={0.75} mt={-0.25}>
<Typography variant="body2" component="div" color="textSecondary" textTransform="uppercase">
{props.category}
</Typography>
<Typography
variant="body1"
fontWeight={700}
gutterBottom
sx={{
display: '-webkit-box',
WebkitLineClamp: '2',
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}>
{props.title}
</Typography>
<Typography
variant="body2"
color="textSecondary"
sx={{
display: '-webkit-box',
maxWidth: '92ch',
WebkitLineClamp: '2',
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}>
{props.description}
</Typography>
<Stack>
<Typography
variant="body1"
fontWeight={700}
sx={{
display: '-webkit-box',
WebkitLineClamp: '2',
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}>
{props.title}
</Typography>
<Typography
variant="body1"
color="textSecondary"
sx={{
display: '-webkit-box',
maxWidth: '92ch',
WebkitLineClamp: '2',
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}>
{props.description}
</Typography>
</Stack>
{props.featureMembers && props.featureMembers?.length && (
<Stack component="ul" mt={1} pl={0} mb={0} display="flex" flexDirection="row" gap={2}>
<Stack component="ul" flexDirection="row" gap={2} mt={0.75} mb={0} p={0}>
{props.featureMembers.map((featureMember) => (
<Typography variant="body2" color="textSecondary" sx={{ display: 'block' }} component="li">
<Typography
key={featureMember}
component="li"
variant="body2"
color="textSecondary"
fontWeight={700}
sx={{ display: 'block' }}>
{featureMember}
</Typography>
))}
</Stack>
)}
</Box>
</Stack>
);
};

Expand Down
233 changes: 113 additions & 120 deletions app/src/components/security/SecurityRuleForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,95 +137,85 @@ const SecurityRuleForm = () => {

return (
<form onSubmit={formikProps.handleSubmit}>
<Box component="fieldset">
<Typography
variant="body1"
color="textSecondary"
sx={{
maxWidth: '72ch'
}}>
Specify reasons why this information should be secured.
<Box component="fieldset" mt={1}>
<Typography component="legend">Secure Records</Typography>
<Typography variant="body1" color="textSecondary" sx={{ mt: -1, mb: 3 }}>
Secure records by adding one or more security rules.
</Typography>
<Autocomplete
value={null}
id={'autocomplete-security-rule-search'}
data-testid={'autocomplete-security-rule-search'}
filterSelectedOptions
clearOnBlur
loading={submissionContext.allSecurityRulesStaticListDataLoader.isLoading}
noOptionsText="No records found"
options={applyRulesAvailableSortedOptions}
filterOptions={(options, state) => {
const searchFilter = createFilterOptions<ISecurityRuleAndCategory>({
ignoreCase: true,
matchFrom: 'any',
stringify: (option) => option.name + option.category_name
});

<Box mt={3}>
<Box mb={2}>
<Typography component="legend">Add Security Rules</Typography>
</Box>
<Autocomplete
value={null}
id={'autocomplete-security-rule-search'}
data-testid={'autocomplete-security-rule-search'}
filterSelectedOptions
clearOnBlur
loading={submissionContext.allSecurityRulesStaticListDataLoader.isLoading}
noOptionsText="No records found"
options={applyRulesAvailableSortedOptions}
filterOptions={(options, state) => {
const searchFilter = createFilterOptions<ISecurityRuleAndCategory>({
ignoreCase: true,
matchFrom: 'any',
stringify: (option) => option.name + option.category_name
});

const selectableOptions = options.filter((securityRule) => {
return !formikProps.values.stagedForApply.some(
(applyingRule) => applyingRule.security_rule_id === securityRule.security_rule_id
);
});

return searchFilter(selectableOptions, state);
}}
getOptionLabel={(option) => option.name}
isOptionEqualToValue={(option, value) => option.security_rule_id === value.security_rule_id}
inputValue={searchText}
onInputChange={(_, value, reason) => {
if (reason === 'reset') {
setSearchText('');
} else {
setSearchText(value);
}
}}
onChange={(_, option) => {
if (option) {
toggleStageApply(option);
}
}}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
placeholder={'Add security reasons'}
fullWidth
InputProps={{
...params.InputProps,
startAdornment: (
<Box mx={1} mt="6px">
<Icon path={mdiMagnify} size={1}></Icon>
</Box>
)
}}
/>
)}
renderOption={(renderProps, renderOption) => {
return (
<ListItem
divider
sx={{
px: 2,
py: '12px !important'
}}
{...renderProps}>
<SecurityRuleCard
title={renderOption.name}
category={renderOption.category_name}
description={renderOption.description}
/>
</ListItem>
const selectableOptions = options.filter((securityRule) => {
return !formikProps.values.stagedForApply.some(
(applyingRule) => applyingRule.security_rule_id === securityRule.security_rule_id
);
}}
/>
</Box>
});

return searchFilter(selectableOptions, state);
}}
getOptionLabel={(option) => option.name}
isOptionEqualToValue={(option, value) => option.security_rule_id === value.security_rule_id}
inputValue={searchText}
onInputChange={(_, value, reason) => {
if (reason === 'reset') {
setSearchText('');
} else {
setSearchText(value);
}
}}
onChange={(_, option) => {
if (option) {
toggleStageApply(option);
}
}}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
placeholder={'Add security rules'}
fullWidth
InputProps={{
...params.InputProps,
startAdornment: (
<Box mx={1} mt="6px">
<Icon path={mdiMagnify} size={1}></Icon>
</Box>
)
}}
/>
)}
renderOption={(renderProps, renderOption) => {
return (
<ListItem
disablePadding
divider
sx={{
py: '12px !important',
px: 2
}}
{...renderProps}>
<SecurityRuleCard
title={renderOption.name}
category={renderOption.category_name}
description={renderOption.description}
/>
</ListItem>
);
}}
/>
<Stack component={TransitionGroup} gap={1} my={1}>
{formikProps.values.stagedForApply.map((applyingRule) => {
return (
Expand All @@ -241,43 +231,46 @@ const SecurityRuleForm = () => {
);
})}
</Stack>
</Box>
<Box component="fieldset" mt={2}>
<Typography component="legend">Secured Records</Typography>

<Box my={2}>
<Typography component="legend">Manage Existing Security</Typography>
<Typography variant="body2">
These rules have already been applied to one or more of the selected features.
</Typography>
</Box>
<Stack component={TransitionGroup} gap={1}>
{groupedAppliedSecurityRules.map((group: IAppliedSecurityRuleGroup) => {
const cardAction = formikProps.values.stagedForRemove.some(
(removingRule) => removingRule.security_rule_id === group.securityRule.security_rule_id
)
? 'remove'
: 'persist';

return (
<Collapse key={group.securityRule.security_rule_id}>
<SecurityRuleActionCard
action={cardAction}
title={group.securityRule.name}
category={group.securityRule.category_name}
description={group.securityRule.description}
featureMembers={group.appliedFeatureGroups.map(
(featureGroup) =>
`${p(featureGroup.numFeatures, featureGroup.displayName)} (${featureGroup.numFeatures})`
)}
onRemove={() => toggleStageRemove(group.securityRule)}
/>
</Collapse>
);
})}
</Stack>
{hasNoSecuritySelected && (
<Alert severity="error" sx={{ marginTop: 1 }}>
<AlertTitle>Open access to all records</AlertTitle>
All users will have unrestricted access to records that have been included in this submission.
{hasNoSecuritySelected ? (
<Alert severity="error">
<AlertTitle>No security applied</AlertTitle>
All users will have unrestricted access to selected records.
</Alert>
) : (
<>
<Typography variant="body1" color="textSecondary" sx={{ mt: -1 }}>
Some of the selected records have been secured using the following rules.
</Typography>
<Stack component={TransitionGroup} gap={1} mt={3}>
{groupedAppliedSecurityRules.map((group: IAppliedSecurityRuleGroup) => {
const cardAction = formikProps.values.stagedForRemove.some(
(removingRule) => removingRule.security_rule_id === group.securityRule.security_rule_id
)
? 'remove'
: 'persist';

return (
<Collapse key={group.securityRule.security_rule_id}>
<SecurityRuleActionCard
action={cardAction}
title={group.securityRule.name}
category={group.securityRule.category_name}
description={group.securityRule.description}
featureMembers={group.appliedFeatureGroups.map(
(featureGroup) =>
`${p(featureGroup.numFeatures, featureGroup.displayName)} (${featureGroup.numFeatures})`
)}
onRemove={() => toggleStageRemove(group.securityRule)}
/>
</Collapse>
);
})}
</Stack>
</>
)}
</Box>
</form>
Expand Down
Loading

0 comments on commit 9ef67c6

Please sign in to comment.