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

mendersoftware / gui / 1057188406

01 Nov 2023 04:24AM UTC coverage: 82.824% (-17.1%) from 99.964%
1057188406

Pull #4134

gitlab-ci

web-flow
chore: Bump uuid from 9.0.0 to 9.0.1

Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.0 to 9.0.1.
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.0...v9.0.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4134: chore: Bump uuid from 9.0.0 to 9.0.1

4349 of 6284 branches covered (0.0%)

8313 of 10037 relevant lines covered (82.82%)

200.97 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] });
12✔
26

27
const useStyles = makeStyles()(theme => ({
12✔
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) => {
12✔
34
  const infoItems = key.split('.');
96✔
35
  let priority = i;
96✔
36
  let title = infoItems[0];
96✔
37
  if (softwareTitleMap[path]) {
96✔
38
    priority = softwareTitleMap[path].priority;
23✔
39
  }
40
  const itemKey = infoItems[infoItems.length - 1];
96✔
41
  let contents = {};
96✔
42
  if (infoItems.length > 2) {
96✔
43
    contents = { content: {}, children: { [infoItems[1]]: mapLayerInformation(infoItems.slice(1).join('.'), value, i + 1, key) } };
50✔
44
  } else {
45
    contents = { content: { [itemKey]: value }, children: {} };
46✔
46
  }
47
  return {
96✔
48
    priority,
49
    key: infoItems.slice(0, -1).join('.'),
50
    title,
51
    ...contents
52
  };
53
};
54

55
const sortAndHoist = thing =>
12✔
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) => {
12!
84
  const { software } = extractSoftware(attributes);
25✔
85

86
  const softwareLayers = software.reduce((accu, item, index) => {
25✔
87
    const layer = mapLayerInformation(item[0], item[1], index, item[0]);
46✔
88
    if (!accu[layer.title]) {
46✔
89
      accu[layer.title] = { content: {}, children: {} };
31✔
90
    }
91
    accu[layer.title] = {
46✔
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
46✔
97
    };
98
    return accu;
46✔
99
  }, {});
100
  if (sort) {
25✔
101
    return sortAndHoist(softwareLayers);
4✔
102
  }
103
  return softwareLayers;
21✔
104
};
105

106
const SoftwareLayer = ({ classes, layer, isNested, overviewOnly, setSnackbar }) => (
12✔
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 }) => {
12✔
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