• 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

98.08
/src/js/components/devices/device-details/installedsoftware.js
1
// Copyright 2021 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 from 'react';
15

16
import { deepmerge } from '@mui/utils';
17
import { makeStyles } from 'tss-react/mui';
18

19
import { rootfsImageVersion, softwareTitleMap } from '../../../constants/releaseConstants';
20
import { extractSoftware, isEmpty } from '../../../helpers';
21
import { TwoColumnData } from '../../common/configurationobject';
22
import DeviceDataCollapse from './devicedatacollapse';
23
import DeviceInventoryLoader from './deviceinventoryloader';
24

25
const borderStyle = theme => ({ borderLeft: 'solid 1px', borderLeftColor: theme.palette.grey[500] });
11✔
26

27
const useStyles = makeStyles()(theme => ({
11✔
28
  paddingOnly: { paddingLeft: theme.spacing(2) },
29
  nestingBorders: { ...borderStyle(theme), paddingLeft: theme.spacing(2), paddingBottom: theme.spacing(2) },
30
  topLevelBorder: { ...borderStyle(theme), paddingBottom: theme.spacing(2), marginBottom: theme.spacing(-2) }
31
}));
32

33
const mapLayerInformation = (key, value, i, path) => {
11✔
34
  const infoItems = key.split('.');
91✔
35
  let priority = i;
91✔
36
  let title = infoItems[0];
91✔
37
  if (softwareTitleMap[path]) {
91✔
38
    priority = softwareTitleMap[path].priority;
18✔
39
  }
40
  const itemKey = infoItems[infoItems.length - 1];
91✔
41
  let contents = {};
91✔
42
  if (infoItems.length > 2) {
91✔
43
    contents = { content: {}, children: { [infoItems[1]]: mapLayerInformation(infoItems.slice(1).join('.'), value, i + 1, key) } };
50✔
44
  } else {
45
    contents = { content: { [itemKey]: value }, children: {} };
41✔
46
  }
47
  return {
91✔
48
    priority,
49
    key: infoItems.slice(0, -1).join('.'),
50
    title,
51
    ...contents
52
  };
53
};
54

55
const sortAndHoist = thing =>
11✔
56
  Object.entries(thing)
38✔
57
    .sort((a, b) => a[1].priority - b[1].priority)
10✔
58
    .reduce((accu, entry) => {
59
      let { children, content, title } = entry[1];
34✔
60
      title = Object.keys(content).reduce((layerTitle, key) => {
34✔
61
        if (softwareTitleMap[`${title}.${key}`]) {
25✔
62
          return softwareTitleMap[`${title}.${key}`].title;
2✔
63
        }
64
        return layerTitle;
23✔
65
      }, title);
66
      children = sortAndHoist(children);
34✔
67
      if (isEmpty(content) && children.length === 1) {
34✔
68
        title = `${title}.${children[0].title}`;
18✔
69
        content = children[0].content;
18✔
70
        children = [];
18✔
71
      }
72
      accu.push({ ...entry[1], children, content, title });
34✔
73
      return accu;
34✔
74
    }, []);
75

76
/**
77
 * to get information about the software installed on the device we first need to:
78
 * - parse the inventory attributes that are likely software references (those with a key ending on '.version')
79
 * - recursively descend the attribute to create a tree and group software based on shared prefixes
80
 * - for a shallower tree rendering the resulting tree is descended recursively once more and all
81
 *    software with only a single "sublayer" is hoisted up & listed under the shared title
82
 */
83
export const extractSoftwareInformation = (attributes = {}, sort = true) => {
11!
84
  const { software } = extractSoftware(attributes);
20✔
85

86
  const softwareLayers = software.reduce((accu, item, index) => {
20✔
87
    const layer = mapLayerInformation(item[0], item[1], index, item[0]);
41✔
88
    if (!accu[layer.title]) {
41✔
89
      accu[layer.title] = { content: {}, children: {} };
26✔
90
    }
91
    accu[layer.title] = {
41✔
92
      ...accu[layer.title],
93
      ...layer,
94
      children: deepmerge(accu[layer.title].children, layer.children),
95
      content: deepmerge(accu[layer.title].content, layer.content),
96
      priority: accu[layer.title].priority < layer.priority ? accu[layer.title].priority : layer.priority
41✔
97
    };
98
    return accu;
41✔
99
  }, {});
100
  if (sort) {
20✔
101
    return sortAndHoist(softwareLayers);
4✔
102
  }
103
  return softwareLayers;
16✔
104
};
105

106
const SoftwareLayer = ({ classes, layer, isNested, overviewOnly, setSnackbar }) => (
11✔
107
  <div className={`margin-top-small ${overviewOnly ? classes.paddingOnly : ''}`}>
11!
108
    <div className="muted">{layer.title}</div>
109
    {!isEmpty(layer.content) && (
20✔
110
      <div className={isNested || overviewOnly ? '' : classes.topLevelBorder}>
22✔
111
        <TwoColumnData
112
          className={`${isNested || overviewOnly ? 'margin-bottom-small' : ''} margin-left-small margin-top-small`}
22✔
113
          config={layer.content}
114
          compact
115
          setSnackbar={setSnackbar}
116
        />
117
      </div>
118
    )}
119
    {!overviewOnly && !!layer.children.length && (
25✔
120
      <div className={classes.nestingBorders}>
121
        {layer.children.map(child => (
122
          <SoftwareLayer classes={classes} key={child.key} layer={child} isNested setSnackbar={setSnackbar} />
6✔
123
        ))}
124
      </div>
125
    )}
126
  </div>
127
);
128

129
export const InstalledSoftware = ({ device, docsVersion, setSnackbar }) => {
11✔
130
  const { classes } = useStyles();
1✔
131

132
  const { attributes = {} } = device;
1!
133

134
  let softwareInformation = extractSoftwareInformation(attributes);
1✔
135

136
  if (!softwareInformation.length) {
1!
137
    softwareInformation = [
×
138
      {
139
        children: [],
140
        content: { [softwareTitleMap[rootfsImageVersion].title]: attributes.artifact_name },
141
        title: softwareTitleMap[rootfsImageVersion].title
142
      }
143
    ];
144
  }
145

146
  const waiting = !Object.values(attributes).some(i => i);
1✔
147
  return (
1✔
148
    <DeviceDataCollapse header={waiting && <DeviceInventoryLoader docsVersion={docsVersion} />} title="Installed software">
1!
149
      <div className={classes.nestingBorders}>
150
        {softwareInformation.map(layer => (
151
          <SoftwareLayer classes={classes} key={layer.key} layer={layer} setSnackbar={setSnackbar} />
5✔
152
        ))}
153
      </div>
154
    </DeviceDataCollapse>
155
  );
156
};
157

158
export default InstalledSoftware;
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