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

mendersoftware / gui / 913068613

pending completion
913068613

Pull #3803

gitlab-ci

web-flow
Merge pull request #3801 from mzedel/men-6383

MEN-6383 - device check in time
Pull Request #3803: staging alignment

4418 of 6435 branches covered (68.66%)

178 of 246 new or added lines in 27 files covered. (72.36%)

8352 of 10138 relevant lines covered (82.38%)

160.95 hits per line

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

68.04
/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 { 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 { DEVICE_STATES, UNGROUPED_GROUP } from '../../constants/deviceConstants';
31
import { rootfsImageVersion, softwareTitleMap } from '../../constants/releaseConstants';
32
import { isEmpty } from '../../helpers';
33
import { getAttributesList, getFeatures, getGroupNames, 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
import MapWrapper from './widgets/mapwidget';
39

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

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

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

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

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

84
export const SoftwareDistribution = () => {
5✔
85
  const dispatch = useDispatch();
694✔
86

87
  const reports = useSelector(
694✔
88
    state =>
89
      getUserSettings(state).reports ||
1,286✔
90
      state.users.globalSettings[`${state.users.currentUser}-reports`] ||
91
      (Object.keys(state.devices.byId).length ? defaultReports : [])
1,286✔
92
  );
93
  // eslint-disable-next-line no-unused-vars
94
  const { [UNGROUPED_GROUP.id]: ungrouped, ...groups } = useSelector(state => state.devices.groups.byId);
1,286✔
95
  const { hasReporting } = useSelector(getFeatures);
694✔
96
  const attributes = useSelector(getAttributesList);
694✔
97
  const hasDevices = useSelector(state => state.devices.byStatus[DEVICE_STATES.accepted].total);
1,286✔
98
  const isEnterprise = useSelector(getIsEnterprise);
694✔
99
  const reportsData = useSelector(state => state.devices.reports);
1,286✔
100
  const groupNames = useSelector(getGroupNames);
694✔
101
  const devicesById = useSelector(state => state.devices.byId);
1,286✔
102

103
  useEffect(() => {
694✔
104
    dispatch(getDeviceAttributes());
10✔
105
    if (hasReporting) {
10!
106
      dispatch(getReportingLimits());
×
107
    }
108
  }, []);
109

110
  useEffect(() => {
694✔
111
    if (hasReporting) {
12!
112
      dispatch(getReportsData());
×
113
      return;
×
114
    }
115
    dispatch(deriveReportsData());
12✔
116
  }, [JSON.stringify(reports)]);
117

118
  const addCurrentSelection = selection => {
694✔
119
    const newReports = [...reports, { ...defaultReports[0], ...selection }];
×
120
    dispatch(saveUserSettings({ reports: newReports }));
×
121
  };
122

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

129
  const removeReport = removedReport => dispatch(saveUserSettings({ reports: reports.filter(report => report !== removedReport) }));
694✔
130

131
  const software = useMemo(() => listSoftware(hasReporting ? attributes : [rootfsImageVersion]), [JSON.stringify(attributes), hasReporting]);
694!
132

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

175
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