• 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

73.44
/src/js/components/dashboard/software-distribution.js
1
// Copyright 2020 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, { useCallback, useEffect, useMemo } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16

17
import { BarChart as BarChartIcon } from '@mui/icons-material';
18

19
import {
20
  defaultReportType,
21
  defaultReports,
22
  deriveReportsData,
23
  getDeviceAttributes,
24
  getDevicesInBounds,
25
  getGroupDevices,
26
  getReportingLimits,
27
  getReportsData
28
} from '../../actions/deviceActions';
29
import { saveUserSettings } from '../../actions/userActions';
30
import { rootfsImageVersion, softwareTitleMap } from '../../constants/releaseConstants';
31
import { isEmpty } from '../../helpers';
32
import {
33
  getAcceptedDevices,
34
  getAttributesList,
35
  getDeviceReports,
36
  getDeviceReportsForUser,
37
  getDevicesById,
38
  getFeatures,
39
  getGroupNames,
40
  getGroupsByIdWithoutUngrouped,
41
  getIsEnterprise
42
} from '../../selectors';
43
import { extractSoftwareInformation } from '../devices/device-details/installedsoftware';
44
import ChartAdditionWidget from './widgets/chart-addition';
45
import DistributionReport from './widgets/distribution';
46
import MapWrapper from './widgets/mapwidget';
47

48
const reportTypes = {
4✔
49
  distribution: DistributionReport
50
};
51

52
const getLayerKey = ({ title, key }, parent) => `${parent.length ? `${parent}.` : parent}${key.length <= title.length ? key : title}`;
16!
53

54
const generateLayer = (softwareLayer, parentKey = '', nestingLevel = 0) => {
4✔
55
  const { children, key, title } = softwareLayer;
16✔
56
  const suffix = title === key ? '.version' : '';
16!
57
  const layerKey = getLayerKey(softwareLayer, parentKey);
16✔
58
  const layerTitle = `${layerKey}${suffix}`;
16✔
59
  let headerItems = [{ title, nestingLevel, value: layerKey }];
16✔
60
  if (softwareTitleMap[layerTitle]) {
16!
61
    headerItems = [
16✔
62
      { subheader: title, nestingLevel, value: `${layerTitle}-subheader` },
63
      { title: softwareTitleMap[layerTitle].title, nestingLevel: nestingLevel + 1, value: layerTitle }
64
    ];
65
  } else if (!isEmpty(children)) {
×
66
    headerItems = [{ subheader: title, nestingLevel, value: `${layerTitle}-subheader` }];
×
67
  }
68
  return Object.values(softwareLayer.children).reduce((accu, childLayer) => {
16✔
69
    const layerData = generateLayer(childLayer, getLayerKey(softwareLayer, parentKey), nestingLevel + 1);
×
70
    accu.push(...layerData);
×
71
    return accu;
×
72
  }, headerItems);
73
};
74

75
const listSoftware = attributes => {
4✔
76
  const enhancedAttributes = attributes.reduce((accu, attribute) => ({ ...accu, [attribute]: attribute }), {});
16✔
77
  const softwareTree = extractSoftwareInformation(enhancedAttributes, false);
16✔
78
  const { rootFs, remainder } = Object.values(softwareTree).reduce(
16✔
79
    (accu, layer) => {
80
      if (layer.key.startsWith('rootfs-image')) {
16!
81
        return { ...accu, rootFs: layer };
16✔
82
      }
83
      accu.remainder.push(layer);
×
84
      return accu;
×
85
    },
86
    { rootFs: undefined, remainder: [] }
87
  );
88

89
  return (rootFs ? [rootFs, ...remainder] : remainder).flatMap(softwareLayer => generateLayer(softwareLayer));
16!
90
};
91

92
export const SoftwareDistribution = () => {
4✔
93
  const dispatch = useDispatch();
1,056✔
94

95
  const reports = useSelector(getDeviceReportsForUser);
1,056✔
96
  const groups = useSelector(getGroupsByIdWithoutUngrouped);
1,056✔
97
  const { hasReporting } = useSelector(getFeatures);
1,056✔
98
  const attributes = useSelector(getAttributesList);
1,056✔
99
  const { total } = useSelector(getAcceptedDevices);
1,056✔
100
  const hasDevices = !!total;
1,056✔
101
  const isEnterprise = useSelector(getIsEnterprise);
1,056✔
102
  const reportsData = useSelector(getDeviceReports);
1,056✔
103
  const groupNames = useSelector(getGroupNames);
1,056✔
104
  const devicesById = useSelector(getDevicesById);
1,056✔
105

106
  useEffect(() => {
1,056✔
107
    dispatch(getDeviceAttributes());
8✔
108
    if (hasReporting) {
8!
109
      dispatch(getReportingLimits());
×
110
    }
111
  }, [dispatch, hasReporting]);
112

113
  useEffect(() => {
1,056✔
114
    if (hasReporting) {
8!
115
      dispatch(getReportsData());
×
116
      return;
×
117
    }
118
    dispatch(deriveReportsData());
8✔
119
    // eslint-disable-next-line react-hooks/exhaustive-deps
120
  }, [dispatch, hasReporting, JSON.stringify(reports)]);
121

122
  const addCurrentSelection = selection => {
1,056✔
123
    const newReports = [...reports, { ...defaultReports[0], ...selection }];
×
124
    dispatch(saveUserSettings({ reports: newReports }));
×
125
  };
126

127
  const onSaveChangedReport = (change, index) => {
1,056✔
128
    let newReports = [...reports];
×
129
    newReports.splice(index, 1, change);
×
130
    dispatch(saveUserSettings({ reports: newReports }));
×
131
  };
132

133
  const removeReport = removedReport => dispatch(saveUserSettings({ reports: reports.filter(report => report !== removedReport) }));
1,056✔
134

135
  const onGetGroupDevices = useCallback((...args) => dispatch(getGroupDevices(...args)), [dispatch]);
1,056✔
136
  const onGetDevicesInBounds = useCallback((...args) => dispatch(getDevicesInBounds(...args)), [dispatch]);
1,056✔
137

138
  // eslint-disable-next-line react-hooks/exhaustive-deps
139
  const software = useMemo(() => listSoftware(hasReporting ? attributes : [rootfsImageVersion]), [JSON.stringify(attributes), hasReporting]);
1,056!
140

141
  if (!isEnterprise) {
1,056✔
142
    return (
1,045✔
143
      <div className="dashboard margin-bottom-large">
144
        <ChartAdditionWidget groups={groups} onAdditionClick={addCurrentSelection} software={software} />
145
      </div>
146
    );
147
  }
148

149
  return hasDevices ? (
11!
150
    <div className="dashboard margin-bottom-large">
151
      {hasReporting && (
11!
152
        <MapWrapper
153
          groups={groups}
154
          groupNames={groupNames}
155
          devicesById={devicesById}
156
          getGroupDevices={onGetGroupDevices}
157
          getDevicesInBounds={onGetDevicesInBounds}
158
        />
159
      )}
160
      {reports.map((report, index) => {
161
        const Component = reportTypes[report.type || defaultReportType];
11!
162
        return (
11✔
163
          <Component
164
            key={`report-${report.group}-${index}`}
165
            data={reportsData[index]}
166
            getGroupDevices={onGetGroupDevices}
167
            groups={groups}
168
            onClick={() => removeReport(report)}
×
169
            onSave={change => onSaveChangedReport(change, index)}
×
170
            selection={report}
171
            software={software}
172
          />
173
        );
174
      })}
175
      <ChartAdditionWidget groups={groups} onAdditionClick={addCurrentSelection} software={software} />
176
    </div>
177
  ) : (
178
    <div className="dashboard-placeholder margin-top-large">
179
      <BarChartIcon style={{ transform: 'scale(5)' }} />
180
      <p className="margin-top-large">Software distribution charts will appear here once you connected a device. </p>
181
    </div>
182
  );
183
};
184

185
export default SoftwareDistribution;
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