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

mendersoftware / gui / 897326496

pending completion
897326496

Pull #3752

gitlab-ci

mzedel
chore(e2e): made use of shared timeout & login checking values to remove code duplication

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3752: chore(e2e-tests): slightly simplified log in test + separated log out test

4395 of 6392 branches covered (68.76%)

8060 of 9780 relevant lines covered (82.41%)

126.17 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}`;
16!
44

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

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

80
  return (rootFs ? [rootFs, ...remainder] : remainder).flatMap(softwareLayer => generateLayer(softwareLayer));
16!
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(() => {
276✔
100
    getDeviceAttributes();
11✔
101
    if (hasReporting) {
11!
102
      getReportingLimits();
×
103
    }
104
  }, []);
105

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

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

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

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

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

131
  if (!isEnterprise) {
276✔
132
    return (
275✔
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}
148
            onClick={() => removeReport(report)}
×
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 ||
154✔
179
    state.users.globalSettings[`${state.users.currentUser}-reports`] ||
180
    (Object.keys(state.devices.byId).length ? defaultReports : []);
154✔
181
  // eslint-disable-next-line no-unused-vars
182
  const { [UNGROUPED_GROUP.id]: ungrouped, ...groups } = state.devices.groups.byId;
154✔
183
  const { hasReporting } = getFeatures(state);
154✔
184
  return {
154✔
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