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

mendersoftware / mender-server / 1651143528

31 Jan 2025 02:49PM UTC coverage: 76.625%. First build
1651143528

push

gitlab-ci

web-flow
Merge pull request #401 from mzedel/feat/release-notes-update

feat(gui): aligned version information w/ monorepo setup & common design

4335 of 6295 branches covered (68.86%)

Branch coverage included in aggregate %.

9 of 11 new or added lines in 2 files covered. (81.82%)

45459 of 58689 relevant lines covered (77.46%)

20.27 hits per line

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

79.27
/frontend/src/js/components/LeftNav.tsx
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 { NavLink } from 'react-router-dom';
17

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

22
import DocsLink from '@northern.tech/common-ui/DocsLink';
23
import MenderTooltip from '@northern.tech/common-ui/MenderTooltip';
24
import storeActions from '@northern.tech/store/actions';
25
import { TIMEOUTS } from '@northern.tech/store/constants';
26
import { getFeatures, getUserCapabilities, getVersionInformation } from '@northern.tech/store/selectors';
27
import copy from 'copy-to-clipboard';
28

29
import { routeConfigs } from '../config/routes';
30

31
const { setSnackbar, setVersionInformation } = storeActions;
2✔
32

33
const listItems = [
2✔
34
  { ...routeConfigs.dashboard, canAccess: ({ userCapabilities: { SPTenant } }) => !SPTenant },
22✔
35
  { ...routeConfigs.devices, canAccess: ({ userCapabilities: { canReadDevices, SPTenant } }) => canReadDevices && !SPTenant },
22✔
36
  {
37
    ...routeConfigs.releases,
38
    canAccess: ({ userCapabilities: { canReadReleases, canUploadReleases, SPTenant } }) => (canReadReleases || canUploadReleases) && !SPTenant
22✔
39
  },
40
  {
41
    ...routeConfigs.deployments,
42
    canAccess: ({ userCapabilities: { canDeploy, canReadDeployments, SPTenant } }) => (canReadDeployments || canDeploy) && !SPTenant
22✔
43
  },
44
  { ...routeConfigs.tenants, canAccess: ({ userCapabilities: { SPTenant } }) => SPTenant },
22✔
45
  { ...routeConfigs.auditlog, canAccess: ({ userCapabilities: { canAuditlog } }) => canAuditlog }
22✔
46
];
47

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

60
const linkables = {
2✔
61
  'Integration': 'integration',
62
  'Mender-Client': 'mender',
63
  'Mender-Artifact': 'mender-artifact',
64
  'Server': 'mender-server'
65
};
66

67
const VersionInfo = () => {
2✔
68
  const [clicks, setClicks] = useState(0);
22✔
69
  const timer = useRef();
22✔
70
  const { classes } = useStyles();
22✔
71

72
  const dispatch = useDispatch();
22✔
73
  const { isHosted } = useSelector(getFeatures);
22✔
74
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
75
  const { latestRelease, ...versionInformation } = useSelector(getVersionInformation);
22✔
76

77
  useEffect(() => {
22✔
78
    return () => {
6✔
79
      clearTimeout(timer.current);
6✔
80
    };
81
  }, []);
82

83
  const onVersionClick = () => {
22✔
84
    copy(JSON.stringify(versionInformation));
×
85
    dispatch(setSnackbar('Version information copied to clipboard'));
×
86
  };
87

88
  const versions = (
89
    <div className={classes.versions}>
22✔
90
      {Object.entries(versionInformation).reduce((accu, [key, version]) => {
91
        if (version) {
92✔
92
          accu.push(
51✔
93
            <React.Fragment key={key}>
94
              {linkables[key] ? (
51✔
95
                <a href={`https://github.com/mendersoftware/${linkables[key]}/tree/${version}`} target="_blank" rel="noopener noreferrer">
96
                  {key}
97
                </a>
98
              ) : (
99
                <div>{key}</div>
100
              )}
101
              <div className="align-right text-overflow" title={version}>
102
                {version}
103
              </div>
104
            </React.Fragment>
105
          );
106
        }
107
        return accu;
92✔
108
      }, [])}
109
    </div>
110
  );
111

112
  const onClick = () => {
22✔
113
    setClicks(clicks + 1);
×
114
    clearTimeout(timer.current);
×
115
    timer.current = setTimeout(() => setClicks(0), TIMEOUTS.threeSeconds);
×
116
    if (clicks > 5) {
×
117
      dispatch(setVersionInformation({ Integration: 'next' }));
×
118
    }
119
    onVersionClick();
×
120
  };
121

122
  let title = versionInformation.Integration ? `Version: ${versionInformation.Integration}` : '';
22✔
123
  if (isHosted && versionInformation.Integration !== 'next') {
22!
124
    title = 'Version: latest';
×
125
  }
126
  return (
22✔
127
    <MenderTooltip arrow title={versions} placement="top">
128
      <div className="clickable slightly-smaller" onClick={onClick}>
129
        {title}
130
      </div>
131
    </MenderTooltip>
132
  );
133
};
134

135
const getDocsLocation = ({ isHosted, isEnterprise }) => {
2✔
136
  if (isHosted) {
22!
NEW
137
    return 'hosted-mender';
×
138
  } else if (isEnterprise) {
22!
NEW
139
    return 'mender-server-enterprise';
×
140
  }
141
  return 'mender-server';
22✔
142
};
143

144
export const LeftNav = () => {
2✔
145
  const releasesRef = useRef();
22✔
146
  const { classes } = useStyles();
22✔
147

148
  const { isEnterprise, isHosted } = useSelector(getFeatures); // here we have to only rely on the enterprise flag, not the tenant setting, to also point hosted enterprise users to the right location
22✔
149
  const userCapabilities = useSelector(getUserCapabilities);
22✔
150

151
  return (
22✔
152
    <div className={`leftFixed leftNav ${classes.list}`}>
153
      <List style={{ padding: 0 }}>
154
        {listItems.reduce((accu, item, index) => {
155
          if (!item.canAccess({ userCapabilities })) {
132✔
156
            return accu;
38✔
157
          }
158
          accu.push(
94✔
159
            <ListItem
160
              className={`navLink leftNav ${classes.navLink}`}
161
              component={NavLink}
162
              end={item.path === ''}
163
              key={index}
164
              ref={item.path === routeConfigs.releases.path ? releasesRef : null}
94✔
165
              to={`/${item.path}`}
166
            >
167
              <ListItemText primary={item.title} style={{ textTransform: 'uppercase' }} />
168
            </ListItem>
169
          );
170
          return accu;
94✔
171
        }, [])}
172
      </List>
173
      <List className={classes.infoList}>
174
        <ListItem className={`navLink leftNav ${classes.listItem}`} component={NavLink} to={`/${routeConfigs.help.path}`}>
175
          <ListItemText primary={routeConfigs.help.title} />
176
        </ListItem>
177
        <ListItem className={classes.listItem}>
178
          <ListItemText
179
            primary={<VersionInfo />}
180
            secondary={
181
              <>
182
                <DocsLink
183
                  className={classes.licenseLink}
184
                  path={`release-information/release-notes-changelog/${getDocsLocation({ isEnterprise, isHosted })}`}
185
                  title="Release information"
186
                />
187
                <br />
188
                <DocsLink className={classes.licenseLink} path="release-information/open-source-licenses" title="License information" />
189
              </>
190
            }
191
            slotProps={{ secondary: { lineHeight: '2em' } }}
192
          />
193
        </ListItem>
194
      </List>
195
    </div>
196
  );
197
};
198

199
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