• 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

75.56
/src/js/components/leftnav.js
1
// Copyright 2018 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, useRef, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16
import { Link, NavLink } from 'react-router-dom';
17

18
// material ui
19
import { List, ListItem, ListItemText, Tooltip } from '@mui/material';
20
import { makeStyles } from 'tss-react/mui';
21

22
import copy from 'copy-to-clipboard';
23

24
import { setSnackbar, setVersionInfo } from '../actions/appActions';
25
import { TIMEOUTS, canAccess } from '../constants/appConstants';
26
import { getFeatures, getUserCapabilities, getVersionInformation } from '../selectors';
27
import DocsLink from './common/docslink';
28

29
const listItems = [
2✔
30
  { route: '/', text: 'Dashboard', canAccess },
31
  { route: '/devices', text: 'Devices', canAccess: ({ userCapabilities: { canReadDevices } }) => canReadDevices },
5✔
32
  { route: '/releases', text: 'Releases', canAccess: ({ userCapabilities: { canReadReleases, canUploadReleases } }) => canReadReleases || canUploadReleases },
5!
33
  { route: '/deployments', text: 'Deployments', canAccess: ({ userCapabilities: { canDeploy, canReadDeployments } }) => canReadDeployments || canDeploy },
5!
34
  { route: '/auditlog', text: 'Audit log', canAccess: ({ userCapabilities: { canAuditlog } }) => canAuditlog }
5✔
35
];
36

37
const useStyles = makeStyles()(theme => ({
10✔
38
  licenseLink: { fontSize: '13px', position: 'relative', top: '6px', color: theme.palette.primary.main },
39
  infoList: { padding: 0, position: 'absolute', bottom: 30, left: 0, right: 0 },
40
  list: {
41
    backgroundColor: theme.palette.background.lightgrey,
42
    borderRight: `1px solid ${theme.palette.grey[300]}`
43
  },
44
  navLink: { padding: '22px 16px 22px 42px' },
45
  listItem: { padding: '16px 16px 16px 42px' },
46
  versions: { display: 'grid', gridTemplateColumns: 'max-content 60px', columnGap: theme.spacing(), '>a': { color: theme.palette.grey[100] } }
47
}));
48

49
const linkables = {
2✔
50
  'Integration': 'integration',
51
  'Mender-Client': 'mender',
52
  'Mender-Artifact': 'mender-artifact',
53
  'GUI': 'gui'
54
};
55

56
const VersionInfo = () => {
2✔
57
  const [clicks, setClicks] = useState(0);
5✔
58
  const timer = useRef();
5✔
59
  const { classes } = useStyles();
5✔
60

61
  const dispatch = useDispatch();
5✔
62
  const { isHosted } = useSelector(getFeatures);
5✔
63
  // eslint-disable-next-line no-unused-vars
64
  const { latestRelease, ...versionInformation } = useSelector(getVersionInformation);
5✔
65

66
  useEffect(() => {
5✔
67
    return () => {
3✔
68
      clearTimeout(timer.current);
3✔
69
    };
70
  }, []);
71

72
  const onVersionClick = () => {
5✔
73
    copy(JSON.stringify(versionInformation));
×
74
    dispatch(setSnackbar('Version information copied to clipboard'));
×
75
  };
76

77
  const versions = (
78
    <div className={classes.versions}>
5✔
79
      {Object.entries(versionInformation).reduce((accu, [key, version]) => {
80
        if (version) {
19✔
81
          accu.push(
10✔
82
            <React.Fragment key={key}>
83
              {linkables[key] ? (
10✔
84
                <a href={`https://github.com/mendersoftware/${linkables[key]}/tree/${version}`} target="_blank" rel="noopener noreferrer">
85
                  {key}
86
                </a>
87
              ) : (
88
                <div>{key}</div>
89
              )}
90
              <div className="align-right text-overflow" title={version}>
91
                {version}
92
              </div>
93
            </React.Fragment>
94
          );
95
        }
96
        return accu;
19✔
97
      }, [])}
98
    </div>
99
  );
100

101
  const onClick = () => {
5✔
102
    setClicks(clicks + 1);
×
103
    clearTimeout(timer.current);
×
104
    timer.current = setTimeout(() => {
×
105
      setClicks(0);
×
106
    }, TIMEOUTS.threeSeconds);
107
    if (clicks > 5) {
×
108
      dispatch(setVersionInfo({ Integration: 'next' }));
×
109
    }
110
    onVersionClick();
×
111
  };
112

113
  let title = versionInformation.Integration ? `Version: ${versionInformation.Integration}` : '';
5✔
114
  if (isHosted && versionInformation.Integration !== 'next') {
5!
115
    title = 'Version: latest';
×
116
  }
117
  return (
5✔
118
    <Tooltip title={versions} placement="top">
119
      <div className="clickable slightly-smaller" onClick={onClick}>
120
        {title}
121
      </div>
122
    </Tooltip>
123
  );
124
};
125

126
export const LeftNav = () => {
2✔
127
  const releasesRef = useRef();
5✔
128
  const { classes } = useStyles();
5✔
129

130
  const userCapabilities = useSelector(getUserCapabilities);
5✔
131
  return (
5✔
132
    <div className={`leftFixed leftNav ${classes.list}`}>
133
      <List style={{ padding: 0 }}>
134
        {listItems.reduce((accu, item, index) => {
135
          if (!item.canAccess({ userCapabilities })) {
25!
136
            return accu;
×
137
          }
138
          accu.push(
25✔
139
            <ListItem
140
              className={`navLink leftNav ${classes.navLink}`}
141
              component={NavLink}
142
              end={item.route === '/'}
143
              key={index}
144
              ref={item.route === '/releases' ? releasesRef : null}
25✔
145
              to={item.route}
146
            >
147
              <ListItemText primary={item.text} style={{ textTransform: 'uppercase' }} />
148
            </ListItem>
149
          );
150
          return accu;
25✔
151
        }, [])}
152
      </List>
153
      <List className={classes.infoList}>
154
        <ListItem className={`navLink leftNav ${classes.listItem}`} component={Link} to="/help">
155
          <ListItemText primary="Help & support" />
156
        </ListItem>
157
        <ListItem className={classes.listItem}>
158
          <ListItemText
159
            primary={<VersionInfo />}
160
            secondary={<DocsLink className={classes.licenseLink} path="release-information/open-source-licenses" title="License information" />}
161
          />
162
        </ListItem>
163
      </List>
164
    </div>
165
  );
166
};
167

168
export default LeftNav;
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