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

mendersoftware / gui / 1350829378

27 Jun 2024 01:46PM UTC coverage: 83.494% (-16.5%) from 99.965%
1350829378

Pull #4465

gitlab-ci

mzedel
chore: test fixes

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #4465: MEN-7169 - feat: added multi sorting capabilities to devices view

4506 of 6430 branches covered (70.08%)

81 of 100 new or added lines in 14 files covered. (81.0%)

1661 existing lines in 163 files now uncovered.

8574 of 10269 relevant lines covered (83.49%)

160.6 hits per line

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

96.23
/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 {
19
  Button,
20
  Checkbox,
21
  Divider,
22
  Drawer,
23
  FormControl,
24
  FormControlLabel,
25
  FormHelperText,
26
  IconButton,
27
  InputLabel,
28
  TextField,
29
  textFieldClasses
30
} from '@mui/material';
31
import { makeStyles } from 'tss-react/mui';
32

33
import validator from 'validator';
34

35
import { mapUserRolesToUiPermissions } from '../../../actions/userActions';
36
import { uiPermissionsByArea, uiPermissionsById } from '../../../constants/userConstants';
37
import { toggle } from '../../../helpers';
38
import { TwoColumnData } from '../../common/configurationobject';
39
import { OAuth2Providers, genericProvider } from '../../login/oauth2providers';
40
import { CopyTextToClipboard } from '../organization/organization';
41
import { UserRolesSelect } from './userform';
42

43
const useStyles = makeStyles()(theme => ({
9✔
44
  actionButtons: { justifyContent: 'flex-end' },
45
  divider: { marginTop: theme.spacing(4) },
46
  leftButton: { marginRight: theme.spacing(2) },
47
  oauthIcon: { fontSize: 36, marginRight: 10 },
48
  userId: { marginTop: theme.spacing(3) },
49
  userIdWrapper: {
50
    // the following 2 lines are required to align the CopyTextToClipboard with the tenant token without sacrificing consistent behaviour
51
    marginBottom: theme.spacing(-3),
52
    '.copy-button': { marginTop: theme.spacing(3) },
53
    [`.${textFieldClasses.root}`]: { width: 400 },
54
    maxWidth: 600
55
  },
56
  widthLimit: { maxWidth: 400 }
57
}));
58

59
export const getUserSSOState = user => {
5✔
60
  const { sso = [] } = user;
55✔
61
  const isOAuth2 = !!sso.length;
55✔
62
  const provider = isOAuth2 ? OAuth2Providers.find(provider => sso.some(({ kind }) => kind.includes(provider.id))) ?? genericProvider : null;
55!
63
  return { isOAuth2, provider };
55✔
64
};
65

66
const mapPermissions = permissions => permissions.map(permission => uiPermissionsById[permission].title).join(', ');
60✔
67

68
const scopedPermissionAreas = ['groups', 'releases'];
5✔
69

70
export const UserId = ({ className = '', userId }) => {
5!
71
  const { classes } = useStyles();
44✔
72
  return (
44✔
73
    <div className={`flexbox space-between ${classes.userIdWrapper} ${className}`}>
74
      <TextField label="User ID" key={userId} InputLabelProps={{ shrink: !!userId }} disabled defaultValue={userId} />
75
      <div className="flexbox center-aligned copy-button">
76
        <CopyTextToClipboard token={userId} />
77
      </div>
78
    </div>
79
  );
80
};
81

82
export const UserDefinition = ({ currentUser, isEnterprise, onCancel, onSubmit, onRemove, roles, selectedUser }) => {
5✔
83
  const { email = '', id } = selectedUser;
44✔
84

85
  const { classes } = useStyles();
44✔
86

87
  const [nameError, setNameError] = useState(false);
44✔
88
  const [hadRoleChanges, setHadRoleChanges] = useState(false);
44✔
89
  const [selectedRoles, setSelectedRoles] = useState([]);
44✔
90
  const [shouldResetPassword, setShouldResetPassword] = useState(false);
44✔
91
  const [currentEmail, setCurrentEmail] = useState('');
44✔
92

93
  useEffect(() => {
44✔
94
    setCurrentEmail(email);
9✔
95
  }, [email]);
96

97
  useEffect(() => {
44✔
98
    setSelectedRoles(selectedUser.roles || []);
9✔
99
  }, [selectedUser.roles]);
100

101
  const validateNameChange = ({ target: { value } }) => {
44✔
102
    setNameError(!validator.isEmail(value) || validator.isEmpty(value));
14✔
103
    setCurrentEmail(value);
14✔
104
  };
105

106
  const onRemoveClick = () => {
44✔
107
    onRemove(selectedUser);
1✔
108
  };
109

110
  const onRolesSelect = (newlySelectedRoles, hadRoleChanges) => {
44✔
111
    setSelectedRoles(newlySelectedRoles);
4✔
112
    setHadRoleChanges(hadRoleChanges);
4✔
113
  };
114

115
  const onSubmitClick = () => {
44✔
116
    if (id && !hadRoleChanges && email === currentEmail) {
1!
UNCOV
117
      return onSubmit(null, 'edit', id, shouldResetPassword ? email : null);
×
118
    }
119
    const changedRoles = hadRoleChanges ? { roles: selectedRoles } : {};
1!
120
    const submissionData = { ...selectedUser, ...changedRoles, email: currentEmail };
1✔
121
    return onSubmit(submissionData, 'edit', id, shouldResetPassword ? currentEmail : null);
1!
122
  };
123

124
  const togglePasswordReset = () => setShouldResetPassword(toggle);
44✔
125

126
  const { areas, groups } = useMemo(() => {
44✔
127
    const emptySelection = { areas: {}, groups: {}, releases: {} };
17✔
128
    if (!(selectedRoles && roles)) {
17!
UNCOV
129
      return emptySelection;
×
130
    }
131

132
    return Object.entries(mapUserRolesToUiPermissions(selectedRoles, roles)).reduce((accu, [key, values]) => {
17✔
133
      if (scopedPermissionAreas.includes(key)) {
85✔
134
        accu[key] = Object.entries(values).reduce((groupsAccu, [name, uiPermissions]) => {
34✔
135
          groupsAccu[name] = mapPermissions(uiPermissions);
9✔
136
          return groupsAccu;
9✔
137
        }, {});
138
      } else {
139
        accu.areas[uiPermissionsByArea[key].title] = mapPermissions(values);
51✔
140
      }
141
      return accu;
85✔
142
    }, emptySelection);
143
  }, [selectedRoles, roles]);
144

145
  const isSubmitDisabled = !selectedRoles.length;
44✔
146

147
  const { isOAuth2, provider } = getUserSSOState(selectedUser);
44✔
148
  const rolesClasses = isEnterprise ? '' : 'muted';
44!
149
  return (
44✔
150
    <Drawer anchor="right" open={!!id} PaperProps={{ style: { minWidth: 600, width: '50vw' } }}>
151
      <div className="flexbox margin-bottom-small space-between">
152
        <h3>Edit user</h3>
153
        <div className="flexbox center-aligned">
154
          {currentUser.id !== id && (
88✔
155
            <Button className={`flexbox center-aligned ${classes.leftButton}`} color="secondary" onClick={onRemoveClick}>
156
              delete user
157
            </Button>
158
          )}
159
          <IconButton onClick={onCancel} aria-label="close">
160
            <CloseIcon />
161
          </IconButton>
162
        </div>
163
      </div>
164
      <Divider />
165
      <UserId className={classes.userId} userId={id} />
166
      <FormControl className={`${classes.widthLimit} margin-top-none`}>
167
        <TextField label="Email" id="email" value={currentEmail} disabled={isOAuth2 || currentUser.id === id} error={nameError} onChange={validateNameChange} />
88✔
168
        {nameError && <FormHelperText className="warning">Please enter a valid email address</FormHelperText>}
56✔
169
      </FormControl>
170
      {isOAuth2 ? (
44!
171
        <div className="flexbox margin-top-small margin-bottom">
172
          <div className={classes.oauthIcon}>{provider.icon}</div>
173
          <div className="info">
174
            This user logs in using their <strong>{provider.name}</strong> account.
175
            <br />
176
            They can connect to {provider.name} to update their login settings.
177
          </div>
178
        </div>
179
      ) : (
180
        <FormControlLabel
181
          control={<Checkbox checked={shouldResetPassword} onChange={togglePasswordReset} />}
182
          label="Send an email to the user containing a link to reset the password"
183
        />
184
      )}
185
      <UserRolesSelect disabled={!isEnterprise} currentUser={currentUser} onSelect={onRolesSelect} roles={roles} user={selectedUser} />
186
      {!!(Object.keys(groups).length || Object.keys(areas).length) && (
151✔
187
        <InputLabel className="margin-top" shrink>
188
          Role permissions
189
        </InputLabel>
190
      )}
191
      <TwoColumnData className={rolesClasses} config={areas} />
192
      {!!Object.keys(groups).length && (
69✔
193
        <>
194
          <InputLabel className="margin-top-small" shrink>
195
            Device groups
196
          </InputLabel>
197
          <TwoColumnData className={rolesClasses} config={groups} />
198
        </>
199
      )}
200
      <Divider className={classes.divider} light />
201
      <div className={`flexbox centered margin-top ${classes.actionButtons}`}>
202
        <Button className={classes.leftButton} onClick={onCancel}>
203
          Cancel
204
        </Button>
205
        <Button color="secondary" variant="contained" disabled={isSubmitDisabled} target="_blank" onClick={onSubmitClick}>
206
          Save
207
        </Button>
208
      </div>
209
    </Drawer>
210
  );
211
};
212

213
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