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

mendersoftware / gui / 1350829378

27 Jun 2024 01:46PM UTC coverage: 83.494% (-16.5%) from 99.965%
1350829378

Pull #4465

gitlab-ci

mzedel
chore: test fixes

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #4465: MEN-7169 - feat: added multi sorting capabilities to devices view

4506 of 6430 branches covered (70.08%)

81 of 100 new or added lines in 14 files covered. (81.0%)

1661 existing lines in 163 files now uncovered.

8574 of 10269 relevant lines covered (83.49%)

160.6 hits per line

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

72.13
/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
  getDeviceAttributes,
23
  getGroupDevices,
24
  getReportingLimits,
25
  getReportsData,
26
  getReportsDataWithoutBackendSupport
27
} from '../../actions/deviceActions';
28
import { saveUserSettings } from '../../actions/userActions';
29
import { rootfsImageVersion, softwareTitleMap } from '../../constants/releaseConstants';
30
import { isEmpty } from '../../helpers';
31
import {
32
  getAcceptedDevices,
33
  getAttributesList,
34
  getDeviceReports,
35
  getDeviceReportsForUser,
36
  getFeatures,
37
  getGroupsByIdWithoutUngrouped,
38
  getIsEnterprise
39
} from '../../selectors';
40
import { extractSoftwareInformation } from '../devices/device-details/installedsoftware';
41
import ChartAdditionWidget from './widgets/chart-addition';
42
import DistributionReport from './widgets/distribution';
43

44
const reportTypes = {
4✔
45
  distribution: DistributionReport
46
};
47

48
const getLayerKey = ({ title, key }, parent) => `${parent.length ? `${parent}.` : parent}${key.length <= title.length ? key : title}`;
19!
49

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

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

85
  return (rootFs ? [rootFs, ...remainder] : remainder).flatMap(softwareLayer => generateLayer(softwareLayer));
19!
86
};
87

88
export const SoftwareDistribution = () => {
4✔
89
  const dispatch = useDispatch();
32✔
90

91
  const reports = useSelector(getDeviceReportsForUser);
32✔
92
  const groups = useSelector(getGroupsByIdWithoutUngrouped);
32✔
93
  const { hasReporting } = useSelector(getFeatures);
32✔
94
  const attributes = useSelector(getAttributesList);
32✔
95
  const { total } = useSelector(getAcceptedDevices);
32✔
96
  const hasDevices = !!total;
32✔
97
  const isEnterprise = useSelector(getIsEnterprise);
32✔
98
  const reportsData = useSelector(getDeviceReports);
32✔
99

100
  useEffect(() => {
32✔
101
    dispatch(getDeviceAttributes());
11✔
102
    if (hasReporting) {
11!
UNCOV
103
      dispatch(getReportingLimits());
×
104
    }
105
  }, [dispatch, hasReporting]);
106

107
  useEffect(() => {
32✔
108
    if (hasReporting) {
12!
UNCOV
109
      dispatch(getReportsData());
×
UNCOV
110
      return;
×
111
    }
112
    dispatch(getReportsDataWithoutBackendSupport());
12✔
113
    // eslint-disable-next-line react-hooks/exhaustive-deps
114
  }, [dispatch, hasReporting, JSON.stringify(reports)]);
115

116
  const addCurrentSelection = selection => {
32✔
UNCOV
117
    const newReports = [...reports, { ...defaultReports[0], ...selection }];
×
UNCOV
118
    dispatch(saveUserSettings({ reports: newReports }));
×
119
  };
120

121
  const onSaveChangedReport = (change, index) => {
32✔
UNCOV
122
    let newReports = [...reports];
×
UNCOV
123
    newReports.splice(index, 1, change);
×
UNCOV
124
    dispatch(saveUserSettings({ reports: newReports }));
×
125
  };
126

127
  const removeReport = removedReport => dispatch(saveUserSettings({ reports: reports.filter(report => report !== removedReport) }));
32✔
128

129
  const onGetGroupDevices = useCallback((...args) => dispatch(getGroupDevices(...args)), [dispatch]);
32✔
130

131
  // eslint-disable-next-line react-hooks/exhaustive-deps
132
  const software = useMemo(() => listSoftware(hasReporting ? attributes : [rootfsImageVersion]), [JSON.stringify(attributes), hasReporting]);
32!
133

134
  if (!isEnterprise) {
32✔
135
    return (
30✔
136
      <div className="dashboard margin-bottom-large">
137
        <ChartAdditionWidget groups={groups} onAdditionClick={addCurrentSelection} software={software} />
138
      </div>
139
    );
140
  }
141

142
  return hasDevices ? (
2!
143
    <div className="dashboard margin-bottom-large">
144
      {reports.map((report, index) => {
145
        const Component = reportTypes[report.type || defaultReportType];
2!
146
        return (
2✔
147
          <Component
148
            key={`report-${report.group}-${index}`}
149
            data={reportsData[index]}
150
            getGroupDevices={onGetGroupDevices}
151
            groups={groups}
UNCOV
152
            onClick={() => removeReport(report)}
×
UNCOV
153
            onSave={change => onSaveChangedReport(change, index)}
×
154
            selection={report}
155
            software={software}
156
          />
157
        );
158
      })}
159
      <ChartAdditionWidget groups={groups} onAdditionClick={addCurrentSelection} software={software} />
160
    </div>
161
  ) : (
162
    <div className="dashboard-placeholder margin-top-large">
163
      <BarChartIcon style={{ transform: 'scale(5)' }} />
164
      <p className="margin-top-large">Software distribution charts will appear here once you connected a device. </p>
165
    </div>
166
  );
167
};
168

169
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