• 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

78.57
/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, FormControlLabel, List } 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
  changeSamlConfig,
29
  deleteSamlConfig,
30
  downloadLicenseReport,
31
  getSamlConfigs,
32
  getUserOrganization,
33
  storeSamlConfig
34
} from '../../../actions/organizationActions';
35
import { TIMEOUTS } from '../../../constants/appConstants';
36
import { createFileDownload, toggle } from '../../../helpers';
37
import { getCurrentSession, getFeatures, getIsEnterprise, getIsPreview, getOrganization, getUserRoles } from '../../../selectors';
38
import ExpandableAttribute from '../../common/expandable-attribute';
39
import { HELPTOOLTIPS, MenderHelpTooltip } from '../../helptips/helptooltips';
40
import Billing from './billing';
41
import OrganizationSettingsItem, { maxWidth } from './organizationsettingsitem';
42
import { SAMLConfig } from './samlconfig';
43

44
const useStyles = makeStyles()(theme => ({
13✔
45
  copyNotification: { height: 30, padding: 15 },
46
  deviceLimitBar: { backgroundColor: theme.palette.grey[500], margin: '15px 0' },
47
  ssoToggle: { width: `calc(${maxWidth}px + ${theme.spacing(4)})` },
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
}));
53

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

64
export const CopyTextToClipboard = ({ onCopy, token }) => {
5✔
65
  const [copied, setCopied] = useState(false);
22✔
66
  const { classes } = useStyles();
22✔
67

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

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

84
export const Organization = () => {
5✔
85
  const [hasSingleSignOn, setHasSingleSignOn] = useState(false);
13✔
86
  const [isConfiguringSSO, setIsConfiguringSSO] = useState(false);
12✔
87
  const [isResettingSSO, setIsResettingSSO] = useState(false);
12✔
88
  const [showTokenWarning, setShowTokenWarning] = useState(false);
12✔
89
  const isEnterprise = useSelector(getIsEnterprise);
12✔
90
  const { isAdmin } = useSelector(getUserRoles);
12✔
91
  const canPreview = useSelector(getIsPreview);
12✔
92
  const { isHosted } = useSelector(getFeatures);
12✔
93
  const org = useSelector(getOrganization);
12✔
94
  const samlConfigs = useSelector(state => state.organization.samlConfigs);
22✔
95
  const dispatch = useDispatch();
12✔
96
  const { token } = useSelector(getCurrentSession);
12✔
97

98
  const { classes } = useStyles();
12✔
99

100
  useEffect(() => {
12✔
101
    dispatch(getUserOrganization());
3✔
102
  }, [dispatch]);
103

104
  useEffect(() => {
12✔
105
    if (isEnterprise) {
3!
106
      dispatch(getSamlConfigs());
3✔
107
    }
108
  }, [dispatch, isEnterprise]);
109

110
  useEffect(() => {
12✔
111
    setHasSingleSignOn(!!samlConfigs.length);
6✔
112
    setIsConfiguringSSO(!!samlConfigs.length);
6✔
113
  }, [samlConfigs.length]);
114

115
  const dispatchedSetSnackbar = useCallback((...args) => dispatch(setSnackbar(...args)), [dispatch]);
12✔
116

117
  const onSSOClick = () => {
12✔
118
    if (hasSingleSignOn) {
2!
119
      setIsConfiguringSSO(false);
2✔
120
      return setIsResettingSSO(true);
2✔
121
    }
122
    setIsConfiguringSSO(toggle);
×
123
  };
124

125
  const onCancelSSOSettings = () => {
12✔
126
    setIsResettingSSO(false);
×
127
    setIsConfiguringSSO(hasSingleSignOn);
×
128
  };
129

130
  const onSaveSSOSettings = useCallback(
12✔
131
    (id, fileContent) => {
132
      if (isResettingSSO) {
2!
133
        return dispatch(deleteSamlConfig(samlConfigs[0])).then(() => setIsResettingSSO(false));
2✔
134
      }
135
      if (id) {
×
136
        return dispatch(changeSamlConfig({ id, config: fileContent }));
×
137
      }
138
      return dispatch(storeSamlConfig(fileContent));
×
139
    },
140
    [isResettingSSO, dispatch, samlConfigs]
141
  );
142

143
  const onTokenExpansion = useCallback(() => setShowTokenWarning(true), []);
12✔
144

145
  const onDownloadReportClick = () =>
12✔
146
    dispatch(downloadLicenseReport()).then(report => createFileDownload(report, `Mender-license-report-${moment().format(moment.HTML5_FMT.DATE)}`, token));
×
147

148
  const onTenantInfoClick = () => {
12✔
149
    copy(`Organization: ${org.name}, Tenant ID: ${org.id}`);
×
150
    setSnackbar('Copied to clipboard');
×
151
  };
152

153
  return (
12✔
154
    <div className="margin-top-small">
155
      <h2 className="margin-top-small">Organization and billing</h2>
156
      <List>
157
        <OrganizationSettingsItem
158
          title="Organization name"
159
          content={{
160
            action: { action: onTenantInfoClick, internal: true },
161
            description: (
162
              <div className={`clickable ${classes.tenantInfo}`} onClick={onTenantInfoClick}>
163
                {org.name}
164
                <span>({org.id})</span>
165
              </div>
166
            )
167
          }}
168
        />
169
        <OrganizationSettingsItem
170
          title={<OrgHeader />}
171
          content={{}}
172
          secondary={
173
            <>
174
              <ExpandableAttribute
175
                className={classes.tenantToken}
176
                component="div"
177
                disableGutters
178
                dividerDisabled
179
                key="org_token"
180
                onExpansion={onTokenExpansion}
181
                secondary={org.tenant_token}
182
                textClasses={{ secondary: 'inventory-text tenant-token-text' }}
183
              />
184
              {showTokenWarning && (
12!
185
                <p className="warning">
186
                  <b>Important</b>
187
                  <br />
188
                  Do not share your organization token with others. Treat this token like a password, as it can be used to request authorization for new
189
                  devices.
190
                </p>
191
              )}
192
            </>
193
          }
194
          sideBarContent={<CopyTextToClipboard onCopy={onTokenExpansion} token={org.tenant_token} />}
195
        />
196
      </List>
197
      {isEnterprise && isAdmin && (
36✔
198
        <div className="flexbox center-aligned">
199
          <FormControlLabel
200
            className={`margin-bottom-small ${classes.ssoToggle}`}
201
            control={<Checkbox checked={!isResettingSSO && (hasSingleSignOn || isConfiguringSSO)} onChange={onSSOClick} />}
26✔
202
            label="Enable SAML single sign-on"
203
          />
204
          {isResettingSSO && !isConfiguringSSO && (
20✔
205
            <>
206
              <Button onClick={onCancelSSOSettings}>Cancel</Button>
207
              <Button onClick={onSaveSSOSettings} disabled={!hasSingleSignOn} variant="contained">
208
                Save
209
              </Button>
210
            </>
211
          )}
212
        </div>
213
      )}
214
      <Collapse className="margin-left-large" in={isConfiguringSSO}>
215
        <SAMLConfig configs={samlConfigs} onSave={onSaveSSOSettings} onCancel={onCancelSSOSettings} setSnackbar={dispatchedSetSnackbar} token={token} />
216
      </Collapse>
217
      {isHosted && <Billing />}
24✔
218
      {(canPreview || !isHosted) && isEnterprise && isAdmin && (
24!
219
        <Button className="margin-top" onClick={onDownloadReportClick} variant="contained">
220
          Download license report
221
        </Button>
222
      )}
223
    </div>
224
  );
225
};
226

227
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