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

mendersoftware / gui / 1493849842

13 Oct 2024 07:39AM UTC coverage: 83.457% (-16.5%) from 99.965%
1493849842

Pull #4531

gitlab-ci

web-flow
chore: Bump send and express in /tests/e2e_tests

Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4531: chore: Bump send and express in /tests/e2e_tests

4486 of 6422 branches covered (69.85%)

8551 of 10246 relevant lines covered (83.46%)

151.3 hits per line

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

61.06
/src/js/components/devices/device-groups.js
1
// Copyright 2018 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, useRef, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16
import { useLocation, useParams } from 'react-router-dom';
17

18
import { AddCircle as AddIcon } from '@mui/icons-material';
19
import { Dialog, DialogContent, DialogTitle } from '@mui/material';
20

21
import pluralize from 'pluralize';
22

23
import { setOfflineThreshold } from '../../actions/appActions';
24
import {
25
  addDynamicGroup,
26
  addStaticGroup,
27
  removeDevicesFromGroup,
28
  removeDynamicGroup,
29
  removeStaticGroup,
30
  selectGroup,
31
  setDeviceFilters,
32
  setDeviceListState,
33
  updateDynamicGroup
34
} from '../../actions/deviceActions';
35
import { setShowConnectingDialog } from '../../actions/userActions';
36
import { SORTING_OPTIONS } from '../../constants/appConstants';
37
import { DEVICE_FILTERING_OPTIONS, DEVICE_ISSUE_OPTIONS, DEVICE_STATES, emptyFilter } from '../../constants/deviceConstants';
38
import { onboardingSteps } from '../../constants/onboardingConstants';
39
import { toggle } from '../../helpers';
40
import {
41
  getAcceptedDevices,
42
  getDeviceCountsByStatus,
43
  getDeviceFilters,
44
  getDeviceLimit,
45
  getFeatures,
46
  getGroups as getGroupsSelector,
47
  getIsEnterprise,
48
  getIsPreview,
49
  getLimitMaxed,
50
  getOnboardingState,
51
  getSelectedGroupInfo,
52
  getSortedFilteringAttributes,
53
  getTenantCapabilities,
54
  getUserCapabilities
55
} from '../../selectors';
56
import { useLocationParams } from '../../utils/liststatehook';
57
import { getOnboardingComponentFor } from '../../utils/onboardingmanager';
58
import Global from '../settings/global';
59
import AuthorizedDevices from './authorized-devices';
60
import DeviceStatusNotification from './devicestatusnotification';
61
import MakeGatewayDialog from './dialogs/make-gateway-dialog';
62
import PreauthDialog, { DeviceLimitWarning } from './dialogs/preauth-dialog';
63
import CreateGroup from './group-management/create-group';
64
import CreateGroupExplainer from './group-management/create-group-explainer';
65
import RemoveGroup from './group-management/remove-group';
66
import Groups from './groups';
67
import DeviceAdditionWidget from './widgets/deviceadditionwidget';
68

69
export const DeviceGroups = () => {
3✔
70
  const [createGroupExplanation, setCreateGroupExplanation] = useState(false);
3✔
71
  const [fromFilters, setFromFilters] = useState(false);
3✔
72
  const [modifyGroupDialog, setModifyGroupDialog] = useState(false);
3✔
73
  const [openIdDialog, setOpenIdDialog] = useState(false);
3✔
74
  const [openPreauth, setOpenPreauth] = useState(false);
3✔
75
  const [showMakeGateway, setShowMakeGateway] = useState(false);
3✔
76
  const [removeGroup, setRemoveGroup] = useState(false);
3✔
77
  const [tmpDevices, setTmpDevices] = useState([]);
3✔
78
  const deviceConnectionRef = useRef();
3✔
79
  const { status: statusParam } = useParams();
3✔
80

81
  const { groupCount, selectedGroup, groupFilters = [] } = useSelector(getSelectedGroupInfo);
3!
82
  const filteringAttributes = useSelector(getSortedFilteringAttributes);
3✔
83
  const { canManageDevices } = useSelector(getUserCapabilities);
3✔
84
  const tenantCapabilities = useSelector(getTenantCapabilities);
3✔
85
  const { groupNames, ...groupsByType } = useSelector(getGroupsSelector);
3✔
86
  const groups = groupNames;
3✔
87
  const { total: acceptedCount = 0 } = useSelector(getAcceptedDevices);
3!
88
  const authRequestCount = useSelector(state => state.monitor.issueCounts.byType[DEVICE_ISSUE_OPTIONS.authRequests.key].total);
18✔
89
  const canPreview = useSelector(getIsPreview);
3✔
90
  const deviceLimit = useSelector(getDeviceLimit);
3✔
91
  const deviceListState = useSelector(state => state.devices.deviceList);
18✔
92
  const features = useSelector(getFeatures);
3✔
93
  const { hasReporting } = features;
3✔
94
  const filters = useSelector(getDeviceFilters);
3✔
95
  const limitMaxed = useSelector(getLimitMaxed);
3✔
96
  const { pending: pendingCount } = useSelector(getDeviceCountsByStatus);
3✔
97
  const showDeviceConnectionDialog = useSelector(state => state.users.showConnectDeviceDialog);
18✔
98
  const onboardingState = useSelector(getOnboardingState);
3✔
99
  const isEnterprise = useSelector(getIsEnterprise);
3✔
100
  const dispatch = useDispatch();
3✔
101
  const isInitialized = useRef(false);
3✔
102
  const location = useLocation();
3✔
103

104
  const [locationParams, setLocationParams] = useLocationParams('devices', {
3✔
105
    filteringAttributes,
106
    filters,
107
    defaults: { sort: { direction: SORTING_OPTIONS.desc } }
108
  });
109

110
  const { refreshTrigger, selectedId, state: selectedState } = deviceListState;
3✔
111

112
  useEffect(() => {
3✔
113
    if (!isInitialized.current) {
2!
114
      return;
2✔
115
    }
116
    setLocationParams({ pageState: deviceListState, filters, selectedGroup });
×
117
    // eslint-disable-next-line react-hooks/exhaustive-deps
118
  }, [
119
    deviceListState.detailsTab,
120
    deviceListState.page,
121
    deviceListState.perPage,
122
    deviceListState.selectedIssues,
123
    // eslint-disable-next-line react-hooks/exhaustive-deps
124
    JSON.stringify(deviceListState.sort),
125
    selectedId,
126
    filters,
127
    selectedGroup,
128
    selectedState,
129
    setLocationParams
130
  ]);
131

132
  useEffect(() => {
3✔
133
    // set isInitialized ref to false when location changes, otherwise when you go back setLocationParams will be set with a duplicate item
134
    isInitialized.current = false;
1✔
135
  }, [location]);
136

137
  useEffect(() => {
3✔
138
    const { groupName, filters = [], id = [], ...remainder } = locationParams;
1!
139
    const { hasFullFiltering } = tenantCapabilities;
1✔
140
    if (groupName) {
1!
141
      if (groupName != selectedGroup) {
1!
142
        dispatch(selectGroup(groupName, filters));
×
143
      }
144
    } else if (filters.length) {
×
145
      // dispatch setDeviceFilters even when filters are empty, otherwise filter will not be reset
146
      dispatch(setDeviceFilters(filters));
×
147
    }
148
    // preset selectedIssues and selectedId with empty values, in case if remain properties are missing them
149
    let listState = { ...remainder };
1✔
150
    if (statusParam && Object.values(DEVICE_STATES).some(state => state === statusParam)) {
1!
151
      listState.state = statusParam;
×
152
    }
153

154
    if (id.length === 1 && Boolean(locationParams.open)) {
1!
155
      listState.selectedId = id[0];
×
156
    } else if (id.length && hasFullFiltering) {
1!
157
      dispatch(setDeviceFilters([...filters, { ...emptyFilter, key: 'id', operator: DEVICE_FILTERING_OPTIONS.$in.key, value: id }]));
×
158
    }
159
    dispatch(setDeviceListState(listState)).then(() => {
1✔
160
      if (isInitialized.current) {
1!
161
        return;
×
162
      }
163
      isInitialized.current = true;
1✔
164
      dispatch(setDeviceListState({}, true, true));
1✔
165
      dispatch(setOfflineThreshold());
1✔
166
    });
167
    // eslint-disable-next-line react-hooks/exhaustive-deps
168
  }, [dispatch, JSON.stringify(tenantCapabilities), JSON.stringify(locationParams), statusParam]);
169

170
  /*
171
   * Groups
172
   */
173
  const removeCurrentGroup = () => {
3✔
174
    const request = groupFilters.length ? dispatch(removeDynamicGroup(selectedGroup)) : dispatch(removeStaticGroup(selectedGroup));
×
175
    return request.then(toggleGroupRemoval).catch(console.log);
×
176
  };
177

178
  // Edit groups from device selection
179
  const addDevicesToGroup = tmpDevices => {
3✔
180
    // (save selected devices in state, open dialog)
181
    setTmpDevices(tmpDevices);
×
182
    setModifyGroupDialog(toggle);
×
183
  };
184

185
  const createGroupFromDialog = (devices, group) => {
3✔
186
    let request = fromFilters ? dispatch(addDynamicGroup(group, filters)) : dispatch(addStaticGroup(group, devices));
×
187
    return request.then(() => {
×
188
      // reached end of list
189
      setCreateGroupExplanation(false);
×
190
      setModifyGroupDialog(false);
×
191
      setFromFilters(false);
×
192
    });
193
  };
194

195
  const onGroupClick = () => {
3✔
196
    if (selectedGroup && groupFilters.length) {
×
197
      return dispatch(updateDynamicGroup(selectedGroup, filters));
×
198
    }
199
    setModifyGroupDialog(true);
×
200
    setFromFilters(true);
×
201
  };
202

203
  const onRemoveDevicesFromGroup = devices => {
3✔
204
    const isGroupRemoval = devices.length >= groupCount;
×
205
    let request;
206
    if (isGroupRemoval) {
×
207
      request = dispatch(removeStaticGroup(selectedGroup));
×
208
    } else {
209
      request = dispatch(removeDevicesFromGroup(selectedGroup, devices));
×
210
    }
211
    return request.catch(console.log);
×
212
  };
213

214
  const openSettingsDialog = e => {
3✔
215
    e.preventDefault();
×
216
    setOpenIdDialog(toggle);
×
217
  };
218

219
  const onCreateGroupClose = () => {
3✔
220
    setModifyGroupDialog(false);
×
221
    setFromFilters(false);
×
222
    setTmpDevices([]);
×
223
  };
224

225
  const onPreauthSaved = addMore => {
3✔
226
    setOpenPreauth(!addMore);
×
227
    dispatch(setDeviceListState({ page: 1, refreshTrigger: !refreshTrigger }));
×
228
  };
229

230
  const onShowDeviceStateClick = state => {
3✔
231
    dispatch(selectGroup());
×
232
    dispatch(setDeviceListState({ state }));
×
233
  };
234

235
  const onGroupSelect = groupName => {
3✔
236
    dispatch(selectGroup(groupName));
×
237
    dispatch(setDeviceListState({ page: 1, refreshTrigger: !refreshTrigger, selection: [] }));
×
238
  };
239

240
  const onShowAuthRequestDevicesClick = () => {
3✔
241
    dispatch(setDeviceFilters([]));
×
242
    dispatch(setDeviceListState({ selectedIssues: [DEVICE_ISSUE_OPTIONS.authRequests.key], page: 1 }));
×
243
  };
244

245
  const toggleGroupRemoval = () => setRemoveGroup(toggle);
3✔
246

247
  const toggleMakeGatewayClick = () => setShowMakeGateway(toggle);
3✔
248

249
  let onboardingComponent;
250
  if (deviceConnectionRef.current && !(pendingCount || acceptedCount)) {
3!
251
    const anchor = { top: deviceConnectionRef.current.offsetTop + deviceConnectionRef.current.offsetHeight / 2, left: deviceConnectionRef.current.offsetLeft };
×
252
    onboardingComponent = getOnboardingComponentFor(
×
253
      onboardingSteps.DEVICES_DELAYED_ONBOARDING,
254
      onboardingState,
255
      { anchor, place: 'left' },
256
      onboardingComponent
257
    );
258
  }
259
  return (
3✔
260
    <>
261
      <div className="tab-container with-sub-panels" style={{ paddingTop: 0, paddingBottom: 45, minHeight: 'max-content', alignContent: 'center' }}>
262
        <h3 className="flexbox center-aligned" style={{ marginBottom: 0, marginTop: 0, flexWrap: 'wrap' }}>
263
          Devices
264
        </h3>
265
        <span className="flexbox space-between margin-left-large margin-right center-aligned padding-top-small">
266
          {hasReporting && !!authRequestCount && (
3!
267
            <a className="flexbox center-aligned margin-right-large" onClick={onShowAuthRequestDevicesClick}>
268
              <AddIcon fontSize="small" style={{ marginRight: 6 }} />
269
              {authRequestCount} new device authentication {pluralize('request', authRequestCount)}
270
            </a>
271
          )}
272
          {!!pendingCount && !selectedGroup && selectedState !== DEVICE_STATES.pending ? (
9!
273
            <DeviceStatusNotification deviceCount={pendingCount} state={DEVICE_STATES.pending} onClick={onShowDeviceStateClick} />
274
          ) : (
275
            <div />
276
          )}
277
          {canManageDevices && (
6✔
278
            <DeviceAdditionWidget
279
              features={features}
280
              onConnectClick={() => dispatch(setShowConnectingDialog(true))}
×
281
              onMakeGatewayClick={toggleMakeGatewayClick}
282
              onPreauthClick={setOpenPreauth}
283
              tenantCapabilities={tenantCapabilities}
284
              innerRef={deviceConnectionRef}
285
            />
286
          )}
287
          {onboardingComponent}
288
        </span>
289
      </div>
290
      <div className="tab-container with-sub-panels" style={{ padding: 0, height: '100%' }}>
291
        <Groups
292
          className="leftFixed"
293
          acceptedCount={acceptedCount}
294
          changeGroup={onGroupSelect}
295
          groups={groupsByType}
296
          openGroupDialog={setCreateGroupExplanation}
297
          selectedGroup={selectedGroup}
298
        />
299
        <div className="rightFluid relative" style={{ paddingTop: 0 }}>
300
          {limitMaxed && <DeviceLimitWarning acceptedDevices={acceptedCount} deviceLimit={deviceLimit} />}
3!
301
          <AuthorizedDevices
302
            addDevicesToGroup={addDevicesToGroup}
303
            onGroupClick={onGroupClick}
304
            onGroupRemoval={toggleGroupRemoval}
305
            onMakeGatewayClick={toggleMakeGatewayClick}
306
            onPreauthClick={setOpenPreauth}
307
            openSettingsDialog={openSettingsDialog}
308
            removeDevicesFromGroup={onRemoveDevicesFromGroup}
309
            showsDialog={showDeviceConnectionDialog || removeGroup || modifyGroupDialog || createGroupExplanation || openIdDialog || openPreauth}
18✔
310
          />
311
        </div>
312
        {removeGroup && <RemoveGroup onClose={toggleGroupRemoval} onRemove={removeCurrentGroup} />}
3!
313
        {modifyGroupDialog && (
3!
314
          <CreateGroup
315
            addListOfDevices={createGroupFromDialog}
316
            fromFilters={fromFilters}
317
            isCreation={fromFilters || !groups.length}
×
318
            selectedDevices={tmpDevices}
319
            onClose={onCreateGroupClose}
320
          />
321
        )}
322
        {createGroupExplanation && <CreateGroupExplainer isEnterprise={isEnterprise} onClose={() => setCreateGroupExplanation(false)} />}
×
323
        {openIdDialog && (
3!
324
          <Dialog open>
325
            <DialogTitle>Default device identity attribute</DialogTitle>
326
            <DialogContent style={{ overflow: 'hidden' }}>
327
              <Global dialog closeDialog={openSettingsDialog} />
328
            </DialogContent>
329
          </Dialog>
330
        )}
331
        {openPreauth && (
3!
332
          <PreauthDialog
333
            acceptedDevices={acceptedCount}
334
            deviceLimit={deviceLimit}
335
            limitMaxed={limitMaxed}
336
            onSubmit={onPreauthSaved}
337
            onCancel={() => setOpenPreauth(false)}
×
338
          />
339
        )}
340
        {showMakeGateway && <MakeGatewayDialog isPreRelease={canPreview} onCancel={toggleMakeGatewayClick} />}
3!
341
      </div>
342
    </>
343
  );
344
};
345

346
export default DeviceGroups;
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