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

mendersoftware / gui / 901187442

pending completion
901187442

Pull #3795

gitlab-ci

mzedel
feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

Ticket: None
Changelog: None
Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3795: feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

4389 of 6365 branches covered (68.96%)

5 of 5 new or added lines in 1 file covered. (100.0%)

1729 existing lines in 165 files now uncovered.

8274 of 10019 relevant lines covered (82.58%)

144.86 hits per line

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

79.22
/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 { onboardingSteps } from '../constants/onboardingConstants';
27
import { getDocsVersion, getFeatures, getOnboardingState, getTenantCapabilities, getUserCapabilities, getVersionInformation } from '../selectors';
28
import { getOnboardingComponentFor } from '../utils/onboardingmanager';
29

30
const listItems = [
3✔
31
  { route: '/', text: 'Dashboard', canAccess },
32
  { route: '/devices', text: 'Devices', canAccess: ({ userCapabilities: { canReadDevices } }) => canReadDevices },
371✔
33
  { route: '/releases', text: 'Releases', canAccess: ({ userCapabilities: { canReadReleases, canUploadReleases } }) => canReadReleases || canUploadReleases },
371✔
34
  { route: '/deployments', text: 'Deployments', canAccess: ({ userCapabilities: { canDeploy, canReadDeployments } }) => canReadDeployments || canDeploy },
371✔
35
  {
36
    route: '/auditlog',
37
    text: 'Audit log',
38
    canAccess: ({ tenantCapabilities: { hasAuditlogs }, userCapabilities: { canAuditlog } }) => hasAuditlogs && canAuditlog
371!
39
  }
40
];
41

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

54
const linkables = {
3✔
55
  'Integration': 'integration',
56
  'Mender-Client': 'mender',
57
  'Mender-Artifact': 'mender-artifact',
58
  'GUI': 'gui'
59
};
60

61
const VersionInfo = () => {
3✔
62
  const [clicks, setClicks] = useState(0);
373✔
63
  const timer = useRef();
373✔
64
  const { classes } = useStyles();
373✔
65

66
  const dispatch = useDispatch();
373✔
67
  const { isHosted } = useSelector(getFeatures);
373✔
68
  // eslint-disable-next-line no-unused-vars
69
  const { latestRelease, ...versionInformation } = useSelector(getVersionInformation);
373✔
70

71
  useEffect(() => {
373✔
72
    return () => {
6✔
73
      clearTimeout(timer.current);
6✔
74
    };
75
  }, []);
76

77
  const onVersionClick = () => {
373✔
UNCOV
78
    copy(JSON.stringify(versionInformation));
×
UNCOV
79
    dispatch(setSnackbar('Version information copied to clipboard'));
×
80
  };
81

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

106
  const onClick = () => {
373✔
UNCOV
107
    setClicks(clicks + 1);
×
UNCOV
108
    clearTimeout(timer.current);
×
UNCOV
109
    timer.current = setTimeout(() => {
×
UNCOV
110
      setClicks(0);
×
111
    }, TIMEOUTS.threeSeconds);
UNCOV
112
    if (clicks > 5) {
×
UNCOV
113
      dispatch(setVersionInfo({ Integration: 'next' }));
×
114
    }
UNCOV
115
    onVersionClick();
×
116
  };
117

118
  let title = versionInformation.Integration ? `Version: ${versionInformation.Integration}` : '';
373✔
119
  if (isHosted && versionInformation.Integration !== 'next') {
373!
UNCOV
120
    title = 'Version: latest';
×
121
  }
122
  return (
373✔
123
    <Tooltip title={versions} placement="top">
124
      <div className="clickable slightly-smaller" onClick={onClick}>
125
        {title}
126
      </div>
127
    </Tooltip>
128
  );
129
};
130

131
export const LeftNav = () => {
3✔
132
  const releasesRef = useRef();
371✔
133
  const { classes } = useStyles();
371✔
134

135
  const docsVersion = useSelector(getDocsVersion);
371✔
136
  const onboardingState = useSelector(getOnboardingState);
371✔
137
  const tenantCapabilities = useSelector(getTenantCapabilities);
371✔
138
  const userCapabilities = useSelector(getUserCapabilities);
371✔
139

140
  const licenseLink = (
141
    <a
371✔
142
      className={classes.licenseLink}
143
      href={`https://docs.mender.io/${docsVersion}release-information/open-source-licenses`}
144
      rel="noopener noreferrer"
145
      target="_blank"
146
    >
147
      License information
148
    </a>
149
  );
150

151
  let onboardingComponent;
152
  if (releasesRef.current) {
371✔
153
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.APPLICATION_UPDATE_REMINDER_TIP, onboardingState, {
50✔
154
      anchor: {
155
        left: releasesRef.current.offsetWidth - 48,
156
        top: releasesRef.current.offsetTop + releasesRef.current.offsetHeight / 2
157
      },
158
      place: 'right'
159
    });
160
  }
161
  return (
371✔
162
    <div className={`leftFixed leftNav ${classes.list}`}>
163
      <List style={{ padding: 0 }}>
164
        {listItems.reduce((accu, item, index) => {
165
          if (!item.canAccess({ tenantCapabilities, userCapabilities })) {
1,855✔
166
            return accu;
1,316✔
167
          }
168
          accu.push(
539✔
169
            <ListItem
170
              className={`navLink leftNav ${classes.navLink}`}
171
              component={NavLink}
172
              end={item.route === '/'}
173
              key={index}
174
              ref={item.route === '/releases' ? releasesRef : null}
539✔
175
              to={item.route}
176
            >
177
              <ListItemText primary={item.text} style={{ textTransform: 'uppercase' }} />
178
            </ListItem>
179
          );
180
          return accu;
539✔
181
        }, [])}
182
      </List>
183
      {onboardingComponent ? onboardingComponent : null}
371!
184
      <List className={classes.infoList}>
185
        <ListItem className={`navLink leftNav ${classes.listItem}`} component={Link} to="/help">
186
          <ListItemText primary="Help & support" />
187
        </ListItem>
188
        <ListItem className={classes.listItem}>
189
          <ListItemText primary={<VersionInfo />} secondary={licenseLink} />
190
        </ListItem>
191
      </List>
192
    </div>
193
  );
194
};
195

196
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