• 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

63.51
/src/js/components/settings/organization/organization.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, { useCallback, useEffect, useState } from 'react';
15
import CopyToClipboard from 'react-copy-to-clipboard';
16
import { useDispatch, useSelector } from 'react-redux';
17

18
// material ui
19
import { FileCopy as CopyPasteIcon } from '@mui/icons-material';
20
import { Button, Checkbox, Collapse, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, List, MenuItem, Select } from '@mui/material';
21
import { makeStyles } from 'tss-react/mui';
22

23
import copy from 'copy-to-clipboard';
24
import moment from 'moment';
25

26
import { setSnackbar } from '../../../actions/appActions';
27
import {
28
  changeSsoConfig,
29
  deleteSsoConfig,
30
  downloadLicenseReport,
31
  getSsoConfigs,
32
  getUserOrganization,
33
  storeSsoConfig
34
} from '../../../actions/organizationActions';
35
import { TIMEOUTS, yes } from '../../../constants/appConstants';
36
import { SSO_TYPES } from '../../../constants/organizationConstants.js';
37
import { createFileDownload, toggle } from '../../../helpers';
38
import { getCurrentSession, getFeatures, getIsEnterprise, getIsPreview, getOrganization, getSsoConfig, getUserRoles } from '../../../selectors';
39
import ExpandableAttribute from '../../common/expandable-attribute';
40
import { HELPTOOLTIPS, MenderHelpTooltip } from '../../helptips/helptooltips';
41
import Billing from './billing';
42
import OrganizationSettingsItem, { maxWidth } from './organizationsettingsitem';
43
import { SSOConfig } from './ssoconfig';
44

45
const useStyles = makeStyles()(theme => ({
15✔
46
  copyNotification: { height: 15 },
47
  deviceLimitBar: { backgroundColor: theme.palette.grey[500], margin: '15px 0' },
48
  tenantInfo: { marginTop: 11, paddingBottom: 3, 'span': { marginLeft: theme.spacing(0.5), color: theme.palette.text.disabled } },
49
  tenantToken: { width: `calc(${maxWidth}px - ${theme.spacing(4)})` },
50
  tokenTitle: { paddingRight: 10 },
51
  tokenExplanation: { margin: '1em 0' },
52
  ssoSelect: { minWidth: 265 }
53
}));
54

55
export const OrgHeader = () => {
6✔
56
  const { classes } = useStyles();
5✔
57
  return (
5✔
58
    <div className="flexbox center-aligned">
59
      <div className={classes.tokenTitle}>Organization token</div>
60
      <MenderHelpTooltip id={HELPTOOLTIPS.tenantToken.id} disableHoverListener={false} placement="top" />
61
    </div>
62
  );
63
};
64

65
export const CopyTextToClipboard = ({ onCopy = yes, token }) => {
6✔
66
  const [copied, setCopied] = useState(false);
59✔
67
  const { classes } = useStyles();
59✔
68

69
  const onCopied = () => {
59✔
UNCOV
70
    setCopied(true);
×
UNCOV
71
    onCopy();
×
UNCOV
72
    setTimeout(() => setCopied(false), TIMEOUTS.fiveSeconds);
×
73
  };
74

75
  return (
59✔
76
    <div>
77
      <CopyToClipboard text={token} onCopy={onCopied}>
78
        <Button startIcon={<CopyPasteIcon />}>Copy to clipboard</Button>
79
      </CopyToClipboard>
80
      <p className={classes.copyNotification}>{copied && <span className="green fadeIn">Copied to clipboard.</span>}</p>
59!
81
    </div>
82
  );
83
};
84

85
export const Organization = () => {
6✔
86
  const [hasSingleSignOn, setHasSingleSignOn] = useState(false);
4✔
87
  const [isConfiguringSSO, setIsConfiguringSSO] = useState(false);
4✔
88
  const [isResettingSSO, setIsResettingSSO] = useState(false);
4✔
89
  const [showTokenWarning, setShowTokenWarning] = useState(false);
4✔
90
  const [newSso, setNewSso] = useState('');
4✔
91
  const [selectedSsoItem, setSelectedSsoItem] = useState(undefined);
4✔
92
  const isEnterprise = useSelector(getIsEnterprise);
4✔
93
  const { isAdmin } = useSelector(getUserRoles);
4✔
94
  const canPreview = useSelector(getIsPreview);
4✔
95
  const { isHosted } = useSelector(getFeatures);
4✔
96
  const org = useSelector(getOrganization);
4✔
97
  const ssoConfig = useSelector(getSsoConfig);
4✔
98
  const dispatch = useDispatch();
4✔
99
  const { token } = useSelector(getCurrentSession);
4✔
100
  const { classes } = useStyles();
4✔
101

102
  useEffect(() => {
4✔
103
    dispatch(getUserOrganization());
2✔
104
  }, [dispatch]);
105

106
  useEffect(() => {
4✔
107
    dispatch(getSsoConfigs());
2✔
108
  }, [dispatch]);
109

110
  useEffect(() => {
4✔
111
    setHasSingleSignOn(!!ssoConfig);
3✔
112
    setIsConfiguringSSO(!!ssoConfig);
3✔
113
    if (ssoConfig) {
3✔
114
      setSelectedSsoItem(SSO_TYPES[ssoConfig.type]);
1✔
115
    }
116
  }, [ssoConfig]);
117

118
  const dispatchedSetSnackbar = useCallback((...args) => dispatch(setSnackbar(...args)), [dispatch]);
4✔
119

120
  const onSaveSSOSettings = useCallback(
4✔
121
    (id, config) => {
UNCOV
122
      const { contentType } = SSO_TYPES[selectedSsoItem.type];
×
UNCOV
123
      if (isResettingSSO) {
×
UNCOV
124
        return dispatch(deleteSsoConfig(ssoConfig)).then(() => setIsResettingSSO(false));
×
125
      }
UNCOV
126
      if (id) {
×
UNCOV
127
        return dispatch(changeSsoConfig({ id, config, contentType }));
×
128
      }
UNCOV
129
      return dispatch(storeSsoConfig({ config, contentType }));
×
130
    },
131
    [isResettingSSO, dispatch, ssoConfig, selectedSsoItem]
132
  );
133

134
  const onCancelSSOSettings = () => {
4✔
UNCOV
135
    setIsResettingSSO(false);
×
UNCOV
136
    setIsConfiguringSSO(hasSingleSignOn);
×
137
  };
138

139
  const onTokenExpansion = useCallback(() => setShowTokenWarning(true), []);
4✔
140

141
  const onDownloadReportClick = () =>
4✔
UNCOV
142
    dispatch(downloadLicenseReport()).then(report => createFileDownload(report, `Mender-license-report-${moment().format(moment.HTML5_FMT.DATE)}`, token));
×
143

144
  const onTenantInfoClick = () => {
4✔
UNCOV
145
    copy(`Organization: ${org.name}, Tenant ID: ${org.id}`);
×
UNCOV
146
    setSnackbar('Copied to clipboard');
×
147
  };
148

149
  const onSSOClick = () => {
4✔
UNCOV
150
    if (hasSingleSignOn) {
×
UNCOV
151
      setIsConfiguringSSO(false);
×
UNCOV
152
      return setIsResettingSSO(true);
×
153
    }
UNCOV
154
    setIsConfiguringSSO(toggle);
×
155
  };
156

157
  const onSsoSelect = useCallback(
4✔
158
    ({ target: { value: type = '' } }) => {
×
UNCOV
159
      if (ssoConfig) {
×
UNCOV
160
        setNewSso(type);
×
161
      } else {
UNCOV
162
        setSelectedSsoItem(SSO_TYPES[type]);
×
163
      }
164
    },
165
    [ssoConfig]
166
  );
167

168
  const changeSSO = () =>
4✔
UNCOV
169
    dispatch(deleteSsoConfig(ssoConfig)).then(() => {
×
UNCOV
170
      setSelectedSsoItem(SSO_TYPES[newSso]);
×
UNCOV
171
      setIsConfiguringSSO(true);
×
UNCOV
172
      setNewSso('');
×
173
    });
174

175
  return (
4✔
176
    <div className="margin-top-small">
177
      <h2 className="margin-top-small">Organization and billing</h2>
178
      <List>
179
        <OrganizationSettingsItem
180
          title="Organization name"
181
          content={{
182
            action: { action: onTenantInfoClick, internal: true },
183
            description: (
184
              <div className={`clickable ${classes.tenantInfo}`} onClick={onTenantInfoClick}>
185
                {org.name}
186
                <span>({org.id})</span>
187
              </div>
188
            )
189
          }}
190
        />
191
        <OrganizationSettingsItem
192
          title={<OrgHeader />}
193
          content={{}}
194
          secondary={
195
            <>
196
              <ExpandableAttribute
197
                className={classes.tenantToken}
198
                component="div"
199
                disableGutters
200
                dividerDisabled
201
                key="org_token"
202
                onExpansion={onTokenExpansion}
203
                secondary={org.tenant_token}
204
                textClasses={{ secondary: 'inventory-text tenant-token-text' }}
205
              />
206
              {showTokenWarning && (
4!
207
                <p className="warning">
208
                  <b>Important</b>
209
                  <br />
210
                  Do not share your organization token with others. Treat this token like a password, as it can be used to request authorization for new
211
                  devices.
212
                </p>
213
              )}
214
            </>
215
          }
216
          sideBarContent={<CopyTextToClipboard onCopy={onTokenExpansion} token={org.tenant_token} />}
217
        />
218
      </List>
219

220
      {isEnterprise && isAdmin && (
12✔
221
        <div>
222
          <FormControlLabel
223
            className="margin-bottom-small"
224
            control={<Checkbox checked={!isResettingSSO && (hasSingleSignOn || isConfiguringSSO)} onChange={onSSOClick} />}
11✔
225
            label="Enable Single Sign-On"
226
          />
227
        </div>
228
      )}
229

230
      {isConfiguringSSO && (
5✔
231
        <div>
232
          <Select className={classes.ssoSelect} displayEmpty onChange={onSsoSelect} value={selectedSsoItem?.type || ''}>
1!
233
            <MenuItem value="">Select type</MenuItem>
234
            {Object.values(SSO_TYPES).map(item => (
235
              <MenuItem key={item.type} value={item.type}>
2✔
236
                <div className="capitalized-start">{item.title}</div>
237
              </MenuItem>
238
            ))}
239
          </Select>
240
        </div>
241
      )}
242

243
      <div className="flexbox center-aligned">
244
        {isResettingSSO && !isConfiguringSSO && (
4!
245
          <>
246
            <Button onClick={onCancelSSOSettings}>Cancel</Button>
247
            <Button onClick={onSaveSSOSettings} disabled={!hasSingleSignOn} variant="contained">
248
              Save
249
            </Button>
250
          </>
251
        )}
252
      </div>
253
      {selectedSsoItem && (
5✔
254
        <div className="margin-top">
255
          <Collapse className="margin-left-large" in={isConfiguringSSO}>
256
            <SSOConfig
257
              ssoItem={selectedSsoItem}
258
              config={ssoConfig}
259
              onSave={onSaveSSOSettings}
260
              onCancel={onCancelSSOSettings}
261
              setSnackbar={dispatchedSetSnackbar}
262
              token={token}
263
            />
264
          </Collapse>
265
        </div>
266
      )}
267

268
      {isHosted && <Billing />}
8✔
269
      {(canPreview || !isHosted) && isEnterprise && isAdmin && (
8!
270
        <Button className="margin-top" onClick={onDownloadReportClick} variant="contained">
271
          Download license report
272
        </Button>
273
      )}
UNCOV
274
      <ChangeSsoDialog dismiss={() => setNewSso(undefined)} open={!!newSso} submit={changeSSO} />
×
275
    </div>
276
  );
277
};
278

279
const ChangeSsoDialog = ({ dismiss, open, submit }) => (
6✔
280
  <Dialog open={open}>
4✔
281
    <DialogTitle>Change Single Sign-On type</DialogTitle>
282
    <DialogContent style={{ overflow: 'hidden' }}>Are you sure you want to change SSO type? This will lose your current settings.</DialogContent>
283
    <DialogActions>
284
      <Button style={{ marginRight: 10 }} onClick={dismiss}>
285
        Cancel
286
      </Button>
UNCOV
287
      <Button variant="contained" color="primary" onClick={() => submit()}>
×
288
        Change
289
      </Button>
290
    </DialogActions>
291
  </Dialog>
292
);
293

294
export default Organization;
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