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

mendersoftware / gui / 1315689429

03 Jun 2024 09:57AM UTC coverage: 83.446% (-16.5%) from 99.964%
1315689429

Pull #4427

gitlab-ci

mzedel
feat: allowed adding users by user id in user creation dialog

Ticket: MEN-7277
Changelog: Title
Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #4427: MEN-7083 - show user id & allow adding user by id to tenant

4485 of 6406 branches covered (70.01%)

38 of 41 new or added lines in 8 files covered. (92.68%)

1670 existing lines in 162 files now uncovered.

8509 of 10197 relevant lines covered (83.45%)

140.48 hits per line

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

89.29
/src/js/components/settings/user-management/userform.js
1
// Copyright 2017 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
import { useWatch } from 'react-hook-form';
16

17
import { InfoOutlined } from '@mui/icons-material';
18
import {
19
  Checkbox,
20
  Collapse,
21
  Dialog,
22
  DialogActions,
23
  DialogContent,
24
  DialogTitle,
25
  FormControl,
26
  FormHelperText,
27
  InputLabel,
28
  ListItemText,
29
  MenuItem,
30
  Select,
31
  Tooltip
32
} from '@mui/material';
33

34
import pluralize from 'pluralize';
35
import { isUUID } from 'validator';
36

37
import { BENEFITS } from '../../../constants/appConstants';
38
import { rolesById, rolesByName, uiPermissionsById } from '../../../constants/userConstants';
39
import EnterpriseNotification from '../../common/enterpriseNotification';
40
import Form from '../../common/forms/form';
41
import FormCheckbox from '../../common/forms/formcheckbox';
42
import PasswordInput from '../../common/forms/passwordinput';
43
import TextInput from '../../common/forms/textinput';
44

45
export const UserRolesSelect = ({ currentUser, disabled, onSelect, roles, user }) => {
6✔
46
  const [selectedRoleIds, setSelectedRoleIds] = useState(
38✔
47
    (user.roles || [rolesByName.admin]).reduce((accu, roleId) => {
47✔
48
      const foundRole = roles[roleId];
38✔
49
      if (foundRole) {
38✔
50
        accu.push(roleId);
37✔
51
      }
52
      return accu;
38✔
53
    }, [])
54
  );
55

56
  const onInputChange = ({ target: { value } }) => {
38✔
57
    const { roles = [] } = user;
4!
58
    let newlySelectedRoles = value;
4✔
59
    if (value.includes('')) {
4!
UNCOV
60
      newlySelectedRoles = [];
×
61
    }
62
    const hadRoleChanges =
63
      roles.length !== newlySelectedRoles.length || roles.some(currentRoleId => !newlySelectedRoles.some(roleId => currentRoleId === roleId));
4✔
64
    setSelectedRoleIds(newlySelectedRoles);
4✔
65
    onSelect(newlySelectedRoles, hadRoleChanges);
4✔
66
  };
67

68
  const { editableRoles, showRoleUsageNotification } = useMemo(() => {
38✔
69
    const editableRoles = Object.entries(roles).map(([id, role]) => {
9✔
70
      const enabled = selectedRoleIds.some(roleId => id === roleId);
48✔
71
      return { enabled, id, ...role };
48✔
72
    });
73
    const showRoleUsageNotification = selectedRoleIds.reduce((accu, roleId) => {
9✔
74
      const { permissions, uiPermissions } = roles[roleId];
6✔
75
      const hasUiApiAccess = [rolesByName.ci].includes(roleId)
6✔
76
        ? false
77
        : roleId === rolesByName.admin ||
7✔
UNCOV
78
          permissions.some(permission => ![rolesByName.deploymentCreation.action].includes(permission.action)) ||
×
79
          uiPermissions.userManagement.includes(uiPermissionsById.read.value);
80
      if (hasUiApiAccess) {
6✔
81
        return false;
5✔
82
      }
83
      return typeof accu !== 'undefined' ? accu : true;
1!
84
    }, undefined);
85
    return { editableRoles, showRoleUsageNotification };
9✔
86
    // eslint-disable-next-line react-hooks/exhaustive-deps
87
  }, [JSON.stringify(roles), selectedRoleIds]);
88

89
  return (
38✔
90
    <div className="flexbox" style={{ alignItems: 'flex-end' }}>
91
      <FormControl id="roles-form" style={{ maxWidth: 400 }}>
92
        <InputLabel id="roles-selection-label">Roles</InputLabel>
93
        <Select
94
          labelId="roles-selection-label"
95
          id={`roles-selector-${selectedRoleIds.length}`}
96
          disabled={disabled}
97
          multiple
98
          value={selectedRoleIds}
99
          required
100
          onChange={onInputChange}
101
          renderValue={selected => selected.map(role => roles[role].name).join(', ')}
44✔
102
        >
103
          {editableRoles.map(role => (
104
            <MenuItem id={role.id} key={role.id} value={role.id}>
222✔
105
              <Checkbox id={`${role.id}-checkbox`} checked={role.enabled} />
106
              <ListItemText id={`${role.id}-text`} primary={role.name} />
107
            </MenuItem>
108
          ))}
109
        </Select>
110
        {showRoleUsageNotification && (
39✔
111
          <FormHelperText className="info">
112
            The selected {pluralize('role', selectedRoleIds.length)} may prevent {currentUser.email === user.email ? 'you' : <i>{user.email}</i>} from using the
1!
113
            Mender UI.
114
            <br />
115
            Consider adding the <i>{rolesById[rolesByName.readOnly].name}</i> role as well.
116
          </FormHelperText>
117
        )}
118
      </FormControl>
119
      <EnterpriseNotification className="margin-left-small" id={BENEFITS.rbac.id} />
120
    </div>
121
  );
122
};
123

124
const PasswordLabel = () => (
6✔
125
  <div className="flexbox center-aligned">
7✔
126
    Optional
127
    <Tooltip
128
      title={
129
        <>
130
          <p>You can skip setting a password for now - you can opt to send the new user an email containing a password reset link by checking the box below.</p>
131
          <p>Organizations using Single Sign-On or other means of authorization may want to create users with no password.</p>
132
        </>
133
      }
134
    >
135
      <InfoOutlined fontSize="small" className="margin-left-small" />
136
    </Tooltip>
137
  </div>
138
);
139

140
const SsoAssignment = ({ currentUser }) => {
6✔
141
  const { sso = [] } = currentUser;
47✔
142

143
  const password = useWatch({ name: 'password' });
47✔
144
  const shouldResetPassword = useWatch({ name: 'shouldResetPassword' });
47✔
145
  return (
47✔
146
    <FormCheckbox
147
      id="assignToSso"
148
      disabled={!sso.length || password || shouldResetPassword}
47!
149
      className="margin-top-none"
150
      label="Add to organization Single Sign-On provider"
151
    />
152
  );
153
};
154

155
const UserIdentifier = ({ onHasUserId }) => {
6✔
156
  const value = useWatch({ name: 'email', defaultValue: '' });
47✔
157

158
  useEffect(() => {
47✔
159
    onHasUserId(isUUID(value));
29✔
160
  }, [value, onHasUserId]);
161

162
  return <TextInput hint="Email" label="Email or User ID" id="email" validations="isLength:1,isUUID||isEmail,trim" required autocomplete="off" />;
47✔
163
};
164

165
export const UserForm = ({ closeDialog, currentUser, canManageUsers, isEnterprise, roles, submit }) => {
6✔
166
  const [hadRoleChanges, setHadRoleChanges] = useState(false);
9✔
167
  const [selectedRoles, setSelectedRoles] = useState();
9✔
168
  const [isAddingExistingUser, setIsAddingExistingUser] = useState(false);
9✔
169

170
  const onSelect = (newlySelectedRoles, hadRoleChanges) => {
9✔
UNCOV
171
    setSelectedRoles(newlySelectedRoles);
×
UNCOV
172
    setHadRoleChanges(hadRoleChanges);
×
173
  };
174

175
  const onSubmit = data => {
9✔
176
    const { password, ...remainder } = data;
2✔
177
    const roleData = hadRoleChanges ? { roles: selectedRoles } : {};
2!
178
    if (isAddingExistingUser) {
2!
NEW
179
      const { email: userId } = data;
×
NEW
180
      return submit(userId, 'add');
×
181
    }
182
    return submit({ ...remainder, ...roleData, password }, 'create');
2✔
183
  };
184

185
  return (
9✔
186
    <Dialog open={true} fullWidth={true} maxWidth="sm">
187
      <DialogTitle>Add new user</DialogTitle>
188
      <DialogContent style={{ overflowY: 'initial' }}>
189
        <Form
190
          onSubmit={onSubmit}
191
          handleCancel={closeDialog}
192
          submitLabel={`${isAddingExistingUser ? 'Add' : 'Create'} user`}
9!
193
          showButtons={true}
194
          autocomplete="off"
195
        >
196
          <UserIdentifier onHasUserId={setIsAddingExistingUser} />
197
          <Collapse in={!isAddingExistingUser}>
198
            <PasswordInput
199
              id="password"
200
              className="edit-pass"
201
              autocomplete="off"
202
              create
203
              edit={false}
204
              generate
205
              InputLabelProps={{ shrink: true }}
206
              label={<PasswordLabel />}
207
              placeholder="Password"
208
              validations="isLength:8"
209
            />
210
            <FormCheckbox id="shouldResetPassword" label="Send an email to the user containing a link to reset the password" />
211
            <SsoAssignment currentUser={currentUser} />
212
            <UserRolesSelect currentUser={currentUser} disabled={!(canManageUsers && isEnterprise)} onSelect={onSelect} roles={roles} user={{}} />
17✔
213
          </Collapse>
214
        </Form>
215
      </DialogContent>
216
      <DialogActions />
217
    </Dialog>
218
  );
219
};
220

221
export default UserForm;
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