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

mendersoftware / gui / 891984097

pending completion
891984097

Pull #3741

gitlab-ci

mzedel
chore: made use of common truth function across codebase to remove code duplication

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3741: MEN-6487 - fix: added more granular check for device troubleshooting feature

4401 of 6401 branches covered (68.75%)

26 of 27 new or added lines in 8 files covered. (96.3%)

1702 existing lines in 165 files now uncovered.

8060 of 9779 relevant lines covered (82.42%)

123.44 hits per line

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

65.93
/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, { useEffect, useMemo } from 'react';
15
import { connect } 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
  getGroupDevices,
25
  getReportingLimits,
26
  getReportsData,
27
  selectGroup
28
} from '../../actions/deviceActions';
29
import { saveUserSettings } from '../../actions/userActions';
30
import { DEVICE_STATES, UNGROUPED_GROUP } from '../../constants/deviceConstants';
31
import { rootfsImageVersion, softwareTitleMap } from '../../constants/releaseConstants';
32
import { isEmpty } from '../../helpers';
33
import { getAttributesList, getFeatures, getIsEnterprise, getUserSettings } from '../../selectors';
34
import EnterpriseNotification from '../common/enterpriseNotification';
35
import { extractSoftwareInformation } from '../devices/device-details/installedsoftware';
36
import ChartAdditionWidget from './widgets/chart-addition';
37
import DistributionReport from './widgets/distribution';
38

39
const reportTypes = {
5✔
40
  distribution: DistributionReport
41
};
42

43
const getLayerKey = ({ title, key }, parent) => `${parent.length ? `${parent}.` : parent}${key.length <= title.length ? key : title}`;
14!
44

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

66
const listSoftware = attributes => {
5✔
67
  const enhancedAttributes = attributes.reduce((accu, attribute) => ({ ...accu, [attribute]: attribute }), {});
14✔
68
  const softwareTree = extractSoftwareInformation(enhancedAttributes, false);
14✔
69
  const { rootFs, remainder } = Object.values(softwareTree).reduce(
14✔
70
    (accu, layer) => {
71
      if (layer.key.startsWith('rootfs-image')) {
14!
72
        return { ...accu, rootFs: layer };
14✔
73
      }
UNCOV
74
      accu.remainder.push(layer);
×
UNCOV
75
      return accu;
×
76
    },
77
    { rootFs: undefined, remainder: [] }
78
  );
79

80
  return (rootFs ? [rootFs, ...remainder] : remainder).flatMap(softwareLayer => generateLayer(softwareLayer));
14!
81
};
82

83
export const SoftwareDistribution = ({
5✔
84
  attributes,
85
  deriveReportsData,
86
  getReportsData,
87
  getDeviceAttributes,
88
  getGroupDevices,
89
  getReportingLimits,
90
  groups,
91
  hasDevices,
92
  hasReporting,
93
  isEnterprise,
94
  reports,
95
  reportsData,
96
  saveUserSettings,
97
  selectGroup
98
}) => {
99
  useEffect(() => {
232✔
100
    getDeviceAttributes();
10✔
101
    if (hasReporting) {
10!
UNCOV
102
      getReportingLimits();
×
103
    }
104
  }, []);
105

106
  useEffect(() => {
232✔
107
    if (hasReporting) {
11!
UNCOV
108
      getReportsData();
×
UNCOV
109
      return;
×
110
    }
111
    deriveReportsData();
11✔
112
  }, [JSON.stringify(reports)]);
113

114
  const addCurrentSelection = selection => {
232✔
UNCOV
115
    const newReports = [...reports, { ...defaultReports[0], ...selection }];
×
UNCOV
116
    saveUserSettings({ reports: newReports });
×
117
  };
118

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

125
  const removeReport = removedReport => {
232✔
UNCOV
126
    saveUserSettings({ reports: reports.filter(report => report !== removedReport) });
×
127
  };
128

129
  const software = useMemo(() => listSoftware(hasReporting ? attributes : [rootfsImageVersion]), [JSON.stringify(attributes), hasReporting]);
232!
130

131
  if (!isEnterprise) {
232✔
132
    return (
231✔
133
      <div className="flexbox centered">
134
        <EnterpriseNotification isEnterprise={isEnterprise} benefit="actionable insights into the devices you are updating with Mender" />
135
      </div>
136
    );
137
  }
138
  return hasDevices ? (
1!
139
    <div className="dashboard margin-bottom-large">
140
      {reports.map((report, index) => {
141
        const Component = reportTypes[report.type || defaultReportType];
1!
142
        return (
1✔
143
          <Component
144
            key={`report-${report.group}-${index}`}
145
            data={reportsData[index]}
146
            getGroupDevices={getGroupDevices}
147
            groups={groups}
UNCOV
148
            onClick={() => removeReport(report)}
×
UNCOV
149
            onSave={change => onSaveChangedReport(change, index)}
×
150
            selectGroup={selectGroup}
151
            selection={report}
152
            software={software}
153
          />
154
        );
155
      })}
156
      <ChartAdditionWidget groups={groups} onAdditionClick={addCurrentSelection} software={software} />
157
    </div>
158
  ) : (
159
    <div className="dashboard-placeholder margin-top-large">
160
      <BarChartIcon style={{ transform: 'scale(5)' }} />
161
      <p className="margin-top-large">Software distribution charts will appear here once you connected a device. </p>
162
    </div>
163
  );
164
};
165

166
const actionCreators = {
5✔
167
  deriveReportsData,
168
  getDeviceAttributes,
169
  getGroupDevices,
170
  getReportsData,
171
  getReportingLimits,
172
  saveUserSettings,
173
  selectGroup
174
};
175

176
const mapStateToProps = state => {
5✔
177
  const reports =
178
    getUserSettings(state).reports ||
129✔
179
    state.users.globalSettings[`${state.users.currentUser}-reports`] ||
180
    (Object.keys(state.devices.byId).length ? defaultReports : []);
129✔
181
  // eslint-disable-next-line no-unused-vars
182
  const { [UNGROUPED_GROUP.id]: ungrouped, ...groups } = state.devices.groups.byId;
129✔
183
  const { hasReporting } = getFeatures(state);
129✔
184
  return {
129✔
185
    attributes: getAttributesList(state),
186
    groups,
187
    hasDevices: state.devices.byStatus[DEVICE_STATES.accepted].total,
188
    hasReporting,
189
    isEnterprise: getIsEnterprise(state),
190
    reports,
191
    reportsData: state.devices.reports
192
  };
193
};
194

195
export default connect(mapStateToProps, actionCreators)(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