• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / gui / 1081664682

22 Nov 2023 02:11PM UTC coverage: 82.798% (-17.2%) from 99.964%
1081664682

Pull #4214

gitlab-ci

tranchitella
fix: Fixed the infinite page redirects when the back button is pressed

Remove the location and navigate from the useLocationParams.setValue callback
dependencies as they change the set function that is presented in other
useEffect dependencies. This happens when the back button is clicked, which
leads to the location changing infinitely.

Changelog: Title
Ticket: MEN-6847
Ticket: MEN-6796

Signed-off-by: Ihor Aleksandrychiev <ihor.aleksandrychiev@northern.tech>
Signed-off-by: Fabio Tranchitella <fabio.tranchitella@northern.tech>
Pull Request #4214: fix: Fixed the infinite page redirects when the back button is pressed

4319 of 6292 branches covered (0.0%)

8332 of 10063 relevant lines covered (82.8%)

191.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.0
/src/js/components/settings/user-management/userdefinition.js
1
// Copyright 2021 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14
import React, { useEffect, useMemo, useState } from 'react';
15

16
// material ui
17
import { Close as CloseIcon } from '@mui/icons-material';
18
import { Button, Checkbox, Divider, Drawer, FormControl, FormControlLabel, FormHelperText, IconButton, InputLabel, TextField } from '@mui/material';
19
import { makeStyles } from 'tss-react/mui';
20

21
import validator from 'validator';
22

23
import { mapUserRolesToUiPermissions } from '../../../actions/userActions';
24
import { uiPermissionsByArea, uiPermissionsById } from '../../../constants/userConstants';
25
import { toggle } from '../../../helpers';
26
import { TwoColumnData } from '../../common/configurationobject';
27
import { OAuth2Providers, genericProvider } from '../../login/oauth2providers';
28
import { UserRolesSelect } from './userform';
29

30
const useStyles = makeStyles()(theme => ({
5✔
31
  actionButtons: { justifyContent: 'flex-end' },
32
  divider: { marginTop: theme.spacing(4) },
33
  leftButton: { marginRight: theme.spacing(2) },
34
  oauthIcon: { fontSize: 36, marginRight: 10 },
35
  widthLimit: { maxWidth: 400 }
36
}));
37

38
export const getUserSSOState = user => {
5✔
39
  const { sso = [] } = user;
56✔
40
  const isOAuth2 = !!sso.length;
56✔
41
  const provider = isOAuth2 ? OAuth2Providers.find(provider => sso.some(({ kind }) => kind.includes(provider.id))) ?? genericProvider : null;
56!
42
  return { isOAuth2, provider };
56✔
43
};
44

45
const mapPermissions = permissions => permissions.map(permission => uiPermissionsById[permission].title).join(', ');
57✔
46

47
const scopedPermissionAreas = ['groups', 'releases'];
5✔
48

49
export const UserDefinition = ({ currentUser, isEnterprise, onCancel, onSubmit, onRemove, roles, selectedUser }) => {
5✔
50
  const { email = '', id } = selectedUser;
46✔
51

52
  const { classes } = useStyles();
46✔
53

54
  const [nameError, setNameError] = useState(false);
46✔
55
  const [hadRoleChanges, setHadRoleChanges] = useState(false);
46✔
56
  const [selectedRoles, setSelectedRoles] = useState([]);
46✔
57
  const [shouldResetPassword, setShouldResetPassword] = useState(false);
46✔
58
  const [currentEmail, setCurrentEmail] = useState('');
46✔
59

60
  useEffect(() => {
46✔
61
    setCurrentEmail(email);
8✔
62
  }, [email]);
63

64
  useEffect(() => {
46✔
65
    setSelectedRoles(selectedUser.roles || []);
8✔
66
  }, [selectedUser.roles]);
67

68
  const validateNameChange = ({ target: { value } }) => {
46✔
69
    setNameError(!validator.isEmail(value) || validator.isEmpty(value));
14✔
70
    setCurrentEmail(value);
14✔
71
  };
72

73
  const onRemoveClick = () => {
46✔
74
    onRemove(selectedUser);
1✔
75
  };
76

77
  const onRolesSelect = (newlySelectedRoles, hadRoleChanges) => {
46✔
78
    setSelectedRoles(newlySelectedRoles);
4✔
79
    setHadRoleChanges(hadRoleChanges);
4✔
80
  };
81

82
  const onSubmitClick = () => {
46✔
83
    if (id && !hadRoleChanges && email === currentEmail) {
1!
84
      return onSubmit(null, 'edit', id, shouldResetPassword ? email : null);
×
85
    }
86
    const changedRoles = hadRoleChanges ? { roles: selectedRoles } : {};
1!
87
    const submissionData = { ...selectedUser, ...changedRoles, email: currentEmail };
1✔
88
    return onSubmit(submissionData, 'edit', id, shouldResetPassword ? currentEmail : null);
1!
89
  };
90

91
  const togglePasswordReset = () => setShouldResetPassword(toggle);
46✔
92

93
  const { areas, groups } = useMemo(() => {
46✔
94
    const emptySelection = { areas: {}, groups: {}, releases: {} };
16✔
95
    if (!(selectedRoles && roles)) {
16!
96
      return emptySelection;
×
97
    }
98

99
    return Object.entries(mapUserRolesToUiPermissions(selectedRoles, roles)).reduce((accu, [key, values]) => {
16✔
100
      if (scopedPermissionAreas.includes(key)) {
80✔
101
        accu[key] = Object.entries(values).reduce((groupsAccu, [name, uiPermissions]) => {
32✔
102
          groupsAccu[name] = mapPermissions(uiPermissions);
9✔
103
          return groupsAccu;
9✔
104
        }, {});
105
      } else {
106
        accu.areas[uiPermissionsByArea[key].title] = mapPermissions(values);
48✔
107
      }
108
      return accu;
80✔
109
    }, emptySelection);
110
  }, [selectedRoles, roles]);
111

112
  const isSubmitDisabled = !selectedRoles.length;
46✔
113

114
  const { isOAuth2, provider } = getUserSSOState(selectedUser);
46✔
115
  const rolesClasses = isEnterprise ? '' : 'muted';
46!
116
  return (
46✔
117
    <Drawer anchor="right" open={!!id} PaperProps={{ style: { minWidth: 600, width: '50vw' } }}>
118
      <div className="flexbox margin-bottom-small space-between">
119
        <h3>Edit user</h3>
120
        <div className="flexbox center-aligned">
121
          {currentUser.id !== id && (
92✔
122
            <Button className={`flexbox center-aligned ${classes.leftButton}`} color="secondary" onClick={onRemoveClick}>
123
              delete user
124
            </Button>
125
          )}
126
          <IconButton onClick={onCancel} aria-label="close">
127
            <CloseIcon />
128
          </IconButton>
129
        </div>
130
      </div>
131
      <Divider />
132
      <FormControl className={classes.widthLimit}>
133
        <TextField label="Email" id="email" value={currentEmail} disabled={isOAuth2 || currentUser.id === id} error={nameError} onChange={validateNameChange} />
92✔
134
        {nameError && <FormHelperText className="warning">Please enter a valid email address</FormHelperText>}
58✔
135
      </FormControl>
136
      {isOAuth2 ? (
46!
137
        <div className="flexbox margin-top-small margin-bottom">
138
          <div className={classes.oauthIcon}>{provider.icon}</div>
139
          <div className="info">
140
            This user logs in using his <strong>{provider.name}</strong> account.
141
            <br />
142
            He can connect to {provider.name} to update his login settings.
143
          </div>
144
        </div>
145
      ) : (
146
        <FormControlLabel
147
          control={<Checkbox checked={shouldResetPassword} onChange={togglePasswordReset} />}
148
          label="Send an email to the user containing a link to reset the password"
149
        />
150
      )}
151
      <UserRolesSelect disabled={!isEnterprise} currentUser={currentUser} onSelect={onRolesSelect} roles={roles} user={selectedUser} />
152
      {!!(Object.keys(groups).length || Object.keys(areas).length) && (
162✔
153
        <InputLabel className="margin-top" shrink>
154
          Role permissions
155
        </InputLabel>
156
      )}
157
      <TwoColumnData className={rolesClasses} config={areas} />
158
      {!!Object.keys(groups).length && (
68✔
159
        <>
160
          <InputLabel className="margin-top-small" shrink>
161
            Device groups
162
          </InputLabel>
163
          <TwoColumnData className={rolesClasses} config={groups} />
164
        </>
165
      )}
166
      <Divider className={classes.divider} light />
167
      <div className={`flexbox centered margin-top ${classes.actionButtons}`}>
168
        <Button className={classes.leftButton} onClick={onCancel}>
169
          Cancel
170
        </Button>
171
        <Button color="secondary" variant="contained" disabled={isSubmitDisabled} target="_blank" onClick={onSubmitClick}>
172
          Save
173
        </Button>
174
      </div>
175
    </Drawer>
176
  );
177
};
178

179
export default UserDefinition;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc