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

mendersoftware / gui / 1336732209

18 Jun 2024 08:59AM UTC coverage: 83.434% (-16.5%) from 99.965%
1336732209

Pull #4443

gitlab-ci

mzedel
feat: added notification about changes to the device offline threshold

Ticket: MEN-7288
Changelog: Title
Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #4443: MEN-7288 - feat/threshold migration

4493 of 6427 branches covered (69.91%)

33 of 35 new or added lines in 8 files covered. (94.29%)

1680 existing lines in 163 files now uncovered.

8547 of 10244 relevant lines covered (83.43%)

151.26 hits per line

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

88.14
/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
import { useSelector } from 'react-redux';
17

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

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

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

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

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

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

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

126
const PasswordLabel = () => (
6✔
127
  <div className="flexbox center-aligned">
3✔
128
    Optional
129
    <Tooltip
130
      title={
131
        <>
132
          <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>
133
          <p>Organizations using Single Sign-On or other means of authorization may want to create users with no password.</p>
134
        </>
135
      }
136
    >
137
      <InfoOutlined fontSize="small" className="margin-left-small" />
138
    </Tooltip>
139
  </div>
140
);
141

142
const SsoAssignment = ({ currentUser }) => {
6✔
143
  const { sso = [] } = currentUser;
25✔
144

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

157
const UserIdentifier = ({ onHasUserId }) => {
6✔
158
  const value = useWatch({ name: 'email', defaultValue: '' });
25✔
159
  const { hasMultiTenantAccess } = useSelector(getFeatures);
25✔
160

161
  useEffect(() => {
25✔
162
    if (!hasMultiTenantAccess) {
15!
163
      return;
15✔
164
    }
UNCOV
165
    onHasUserId(isUUID(value));
×
166
  }, [hasMultiTenantAccess, value, onHasUserId]);
167

168
  return (
25✔
169
    <TextInput
170
      hint="Email"
171
      label={hasMultiTenantAccess ? 'Email or User ID' : 'Email'}
25!
172
      id="email"
173
      validations={`isLength:1,${hasMultiTenantAccess ? 'isUUID||' : ''}isEmail,trim`}
25!
174
      required
175
      autocomplete="off"
176
    />
177
  );
178
};
179

180
export const UserForm = ({ closeDialog, currentUser, canManageUsers, isEnterprise, roles, submit }) => {
6✔
181
  const [hadRoleChanges, setHadRoleChanges] = useState(false);
3✔
182
  const [selectedRoles, setSelectedRoles] = useState();
3✔
183
  const [isAddingExistingUser, setIsAddingExistingUser] = useState(false);
3✔
184

185
  const onSelect = (newlySelectedRoles, hadRoleChanges) => {
3✔
UNCOV
186
    setSelectedRoles(newlySelectedRoles);
×
UNCOV
187
    setHadRoleChanges(hadRoleChanges);
×
188
  };
189

190
  const onSubmit = data => {
3✔
191
    const { password, ...remainder } = data;
1✔
192
    const roleData = hadRoleChanges ? { roles: selectedRoles } : {};
1!
193
    if (isAddingExistingUser) {
1!
UNCOV
194
      const { email: userId } = data;
×
UNCOV
195
      return submit(userId, 'add');
×
196
    }
197
    return submit({ ...remainder, ...roleData, password }, 'create');
1✔
198
  };
199

200
  return (
3✔
201
    <Dialog open={true} fullWidth={true} maxWidth="sm">
202
      <DialogTitle>Add new user</DialogTitle>
203
      <DialogContent style={{ overflowY: 'initial' }}>
204
        <Form
205
          onSubmit={onSubmit}
206
          handleCancel={closeDialog}
207
          submitLabel={`${isAddingExistingUser ? 'Add' : 'Create'} user`}
3!
208
          showButtons={true}
209
          autocomplete="off"
210
        >
211
          <UserIdentifier onHasUserId={setIsAddingExistingUser} />
212
          <Collapse in={!isAddingExistingUser}>
213
            <PasswordInput
214
              id="password"
215
              className="edit-pass"
216
              autocomplete="off"
217
              create
218
              edit={false}
219
              generate
220
              InputLabelProps={{ shrink: true }}
221
              label={<PasswordLabel />}
222
              placeholder="Password"
223
              validations="isLength:8"
224
            />
225
            <FormCheckbox id="shouldResetPassword" label="Send an email to the user containing a link to reset the password" />
226
            <SsoAssignment currentUser={currentUser} />
227
            <UserRolesSelect currentUser={currentUser} disabled={!(canManageUsers && isEnterprise)} onSelect={onSelect} roles={roles} user={{}} />
5✔
228
          </Collapse>
229
        </Form>
230
      </DialogContent>
231
      <DialogActions />
232
    </Dialog>
233
  );
234
};
235

236
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