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

mendersoftware / gui / 963002358

pending completion
963002358

Pull #3870

gitlab-ci

mzedel
chore: cleaned up left over onboarding tooltips & aligned with updated design

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3870: MEN-5413

4348 of 6319 branches covered (68.81%)

95 of 122 new or added lines in 24 files covered. (77.87%)

1734 existing lines in 160 files now uncovered.

8174 of 9951 relevant lines covered (82.14%)

178.12 hits per line

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

67.02
/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 EnterpriseNotification from '../common/enterpriseNotification';
44
import { extractSoftwareInformation } from '../devices/device-details/installedsoftware';
45
import ChartAdditionWidget from './widgets/chart-addition';
46
import DistributionReport from './widgets/distribution';
47
import MapWrapper from './widgets/mapwidget';
48

49
const reportTypes = {
5✔
50
  distribution: DistributionReport
51
};
52

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

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

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

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

93
export const SoftwareDistribution = () => {
5✔
94
  const dispatch = useDispatch();
1,207✔
95

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

107
  useEffect(() => {
1,207✔
108
    dispatch(getDeviceAttributes());
9✔
109
    if (hasReporting) {
9!
UNCOV
110
      dispatch(getReportingLimits());
×
111
    }
112
  }, [dispatch, hasReporting]);
113

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

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

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

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

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

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

142
  if (!isEnterprise) {
1,207✔
143
    return (
1,196✔
144
      <div className="flexbox centered">
145
        <EnterpriseNotification isEnterprise={isEnterprise} benefit="actionable insights into the devices you are updating with Mender" />
146
      </div>
147
    );
148
  }
149

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

186
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