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

mendersoftware / gui / 1113439055

19 Dec 2023 09:01PM UTC coverage: 82.752% (-17.2%) from 99.964%
1113439055

Pull #4258

gitlab-ci

mender-test-bot
chore: Types update

Signed-off-by: Mender Test Bot <mender@northern.tech>
Pull Request #4258: chore: Types update

4326 of 6319 branches covered (0.0%)

8348 of 10088 relevant lines covered (82.75%)

189.39 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