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

mendersoftware / gui / 951400782

pending completion
951400782

Pull #3900

gitlab-ci

web-flow
chore: bump @testing-library/jest-dom from 5.16.5 to 5.17.0

Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.16.5 to 5.17.0.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.16.5...v5.17.0)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #3900: chore: bump @testing-library/jest-dom from 5.16.5 to 5.17.0

4446 of 6414 branches covered (69.32%)

8342 of 10084 relevant lines covered (82.73%)

186.0 hits per line

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

55.14
/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 { 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, setSnackbar } from '../../actions/appActions';
24
import {
25
  addDynamicGroup,
26
  addStaticGroup,
27
  preauthDevice,
28
  removeDevicesFromGroup,
29
  removeDynamicGroup,
30
  removeStaticGroup,
31
  selectGroup,
32
  setDeviceFilters,
33
  setDeviceListState,
34
  updateDynamicGroup
35
} from '../../actions/deviceActions';
36
import { setShowConnectingDialog } from '../../actions/userActions';
37
import { SORTING_OPTIONS } from '../../constants/appConstants';
38
import { DEVICE_FILTERING_OPTIONS, DEVICE_ISSUE_OPTIONS, DEVICE_STATES, emptyFilter } from '../../constants/deviceConstants';
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
  getSelectedGroupInfo,
51
  getShowHelptips,
52
  getSortedFilteringAttributes,
53
  getTenantCapabilities,
54
  getUserCapabilities
55
} from '../../selectors';
56
import { useLocationParams } from '../../utils/liststatehook';
57
import Global from '../settings/global';
58
import AuthorizedDevices from './authorized-devices';
59
import DeviceStatusNotification from './devicestatusnotification';
60
import MakeGatewayDialog from './dialogs/make-gateway-dialog';
61
import PreauthDialog, { DeviceLimitWarning } from './dialogs/preauth-dialog';
62
import CreateGroup from './group-management/create-group';
63
import CreateGroupExplainer from './group-management/create-group-explainer';
64
import RemoveGroup from './group-management/remove-group';
65
import Groups from './groups';
66
import DeviceAdditionWidget from './widgets/deviceadditionwidget';
67

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

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

101
  const [locationParams, setLocationParams] = useLocationParams('devices', {
2✔
102
    filteringAttributes,
103
    filters,
104
    defaults: { sort: { direction: SORTING_OPTIONS.desc } }
105
  });
106

107
  const { refreshTrigger, selectedId, state: selectedState } = deviceListState;
2✔
108

109
  useEffect(() => {
2✔
110
    if (!deviceTimer.current) {
2!
111
      return;
2✔
112
    }
113
    setLocationParams({ pageState: deviceListState, filters, selectedGroup });
×
114
  }, [
115
    deviceListState.detailsTab,
116
    deviceListState.page,
117
    deviceListState.perPage,
118
    deviceListState.selectedIssues,
119
    JSON.stringify(deviceListState.sort),
120
    selectedId,
121
    filters,
122
    selectedGroup,
123
    selectedState
124
  ]);
125

126
  useEffect(() => {
2✔
127
    if (locationParams.groupName) {
1!
128
      dispatch(selectGroup(locationParams.groupName));
×
129
    }
130
    let listState = { setOnly: true };
1✔
131
    if (locationParams.open && locationParams.id.length) {
1!
132
      listState = { ...listState, selectedId: locationParams.id[0], detailsTab: locationParams.detailsTab };
×
133
    }
134
    if (!locationParams.id?.length && selectedId) {
1!
135
      listState = { ...listState, detailsTab: 'identity' };
×
136
    }
137
    dispatch(setDeviceListState(listState));
1✔
138
  }, [locationParams.detailsTab, locationParams.groupName, JSON.stringify(locationParams.id), locationParams.open]);
139

140
  useEffect(() => {
2✔
141
    const { groupName, filters = [], id = [], ...remainder } = locationParams;
1!
142
    const { hasFullFiltering } = tenantCapabilities;
1✔
143
    if (groupName) {
1!
144
      dispatch(selectGroup(groupName, filters));
×
145
    } else if (filters.length) {
1!
146
      dispatch(setDeviceFilters(filters));
×
147
    }
148
    const state = statusParam && Object.values(DEVICE_STATES).some(state => state === statusParam) ? statusParam : selectedState;
1!
149
    let listState = { ...remainder, state, refreshTrigger: !refreshTrigger };
1✔
150
    if (id.length === 1 && Boolean(locationParams.open)) {
1!
151
      listState.selectedId = id[0];
×
152
    } else if (id.length && hasFullFiltering) {
1!
153
      dispatch(setDeviceFilters([...filters, { ...emptyFilter, key: 'id', operator: DEVICE_FILTERING_OPTIONS.$in.key, value: id }]));
×
154
    }
155
    dispatch(setDeviceListState(listState));
1✔
156
    dispatch(setOfflineThreshold());
1✔
157
  }, []);
158

159
  /*
160
   * Groups
161
   */
162
  const removeCurrentGroup = () => {
2✔
163
    const request = groupFilters.length ? dispatch(removeDynamicGroup(selectedGroup)) : dispatch(removeStaticGroup(selectedGroup));
×
164
    return request.then(toggleGroupRemoval).catch(console.log);
×
165
  };
166

167
  // Edit groups from device selection
168
  const addDevicesToGroup = tmpDevices => {
2✔
169
    // (save selected devices in state, open dialog)
170
    setTmpDevices(tmpDevices);
×
171
    setModifyGroupDialog(toggle);
×
172
  };
173

174
  const createGroupFromDialog = (devices, group) => {
2✔
175
    let request = fromFilters ? dispatch(addDynamicGroup(group, filters)) : dispatch(addStaticGroup(group, devices));
×
176
    return request.then(() => {
×
177
      // reached end of list
178
      setCreateGroupExplanation(false);
×
179
      setModifyGroupDialog(false);
×
180
      setFromFilters(false);
×
181
    });
182
  };
183

184
  const onGroupClick = () => {
2✔
185
    if (selectedGroup && groupFilters.length) {
×
186
      return dispatch(updateDynamicGroup(selectedGroup, filters));
×
187
    }
188
    setModifyGroupDialog(true);
×
189
    setFromFilters(true);
×
190
  };
191

192
  const onRemoveDevicesFromGroup = devices => {
2✔
193
    const isGroupRemoval = devices.length >= groupCount;
×
194
    let request;
195
    if (isGroupRemoval) {
×
196
      request = dispatch(removeStaticGroup(selectedGroup));
×
197
    } else {
198
      request = dispatch(removeDevicesFromGroup(selectedGroup, devices));
×
199
    }
200
    return request.catch(console.log);
×
201
  };
202

203
  const openSettingsDialog = e => {
2✔
204
    e.preventDefault();
×
205
    setOpenIdDialog(toggle);
×
206
  };
207

208
  const onCreateGroupClose = () => {
2✔
209
    setModifyGroupDialog(false);
×
210
    setFromFilters(false);
×
211
    setTmpDevices([]);
×
212
  };
213

214
  const onPreauthSaved = addMore => {
2✔
215
    setOpenPreauth(!addMore);
×
216
    dispatch(setDeviceListState({ page: 1, refreshTrigger: !refreshTrigger }));
×
217
  };
218

219
  const onShowDeviceStateClick = state => {
2✔
220
    dispatch(selectGroup());
×
221
    dispatch(setDeviceListState({ state }));
×
222
  };
223

224
  const onGroupSelect = groupName => {
2✔
225
    dispatch(selectGroup(groupName));
×
226
    dispatch(setDeviceListState({ page: 1, refreshTrigger: !refreshTrigger, selection: [] }));
×
227
  };
228

229
  const onShowAuthRequestDevicesClick = () => {
2✔
230
    dispatch(setDeviceFilters([]));
×
231
    dispatch(setDeviceListState({ selectedIssues: [DEVICE_ISSUE_OPTIONS.authRequests.key], page: 1 }));
×
232
  };
233

234
  const toggleGroupRemoval = () => setRemoveGroup(toggle);
2✔
235

236
  const toggleMakeGatewayClick = () => setShowMakeGateway(toggle);
2✔
237

238
  return (
2✔
239
    <>
240
      <div className="tab-container with-sub-panels margin-bottom-small" style={{ padding: 0, minHeight: 'initial' }}>
241
        <h3 style={{ marginBottom: 0 }}>Devices</h3>
242
        <div className="flexbox space-between margin-left-large margin-right center-aligned padding-bottom padding-top-small">
243
          {hasReporting && !!authRequestCount && (
2!
244
            <a className="flexbox center-aligned margin-right-large" onClick={onShowAuthRequestDevicesClick}>
245
              <AddIcon fontSize="small" style={{ marginRight: 6 }} />
246
              {authRequestCount} new device authentication {pluralize('request', authRequestCount)}
247
            </a>
248
          )}
249
          {!!pendingCount && !selectedGroup && selectedState !== DEVICE_STATES.pending ? (
6!
250
            <DeviceStatusNotification deviceCount={pendingCount} state={DEVICE_STATES.pending} onClick={onShowDeviceStateClick} />
251
          ) : (
252
            <div />
253
          )}
254
          {canManageDevices && (
4✔
255
            <DeviceAdditionWidget
256
              features={features}
257
              onConnectClick={() => dispatch(setShowConnectingDialog(true))}
×
258
              onMakeGatewayClick={toggleMakeGatewayClick}
259
              onPreauthClick={setOpenPreauth}
260
              tenantCapabilities={tenantCapabilities}
261
            />
262
          )}
263
        </div>
264
      </div>
265
      <div className="tab-container with-sub-panels" style={{ padding: 0, height: '100%' }}>
266
        <Groups
267
          className="leftFixed"
268
          acceptedCount={acceptedCount}
269
          changeGroup={onGroupSelect}
270
          groups={groupsByType}
271
          openGroupDialog={setCreateGroupExplanation}
272
          selectedGroup={selectedGroup}
273
          showHelptips={showHelptips}
274
        />
275
        <div className="rightFluid relative" style={{ paddingTop: 0 }}>
276
          {limitMaxed && <DeviceLimitWarning acceptedDevices={acceptedCount} deviceLimit={deviceLimit} />}
2!
277
          <AuthorizedDevices
278
            addDevicesToGroup={addDevicesToGroup}
279
            onGroupClick={onGroupClick}
280
            onGroupRemoval={toggleGroupRemoval}
281
            onMakeGatewayClick={toggleMakeGatewayClick}
282
            onPreauthClick={setOpenPreauth}
283
            openSettingsDialog={openSettingsDialog}
284
            removeDevicesFromGroup={onRemoveDevicesFromGroup}
285
            showsDialog={showDeviceConnectionDialog || removeGroup || modifyGroupDialog || createGroupExplanation || openIdDialog || openPreauth}
12✔
286
          />
287
        </div>
288
        {removeGroup && <RemoveGroup onClose={toggleGroupRemoval} onRemove={removeCurrentGroup} />}
2!
289
        {modifyGroupDialog && (
2!
290
          <CreateGroup
291
            addListOfDevices={createGroupFromDialog}
292
            fromFilters={fromFilters}
293
            isCreation={fromFilters || !groups.length}
×
294
            selectedDevices={tmpDevices}
295
            onClose={onCreateGroupClose}
296
          />
297
        )}
298
        {createGroupExplanation && <CreateGroupExplainer isEnterprise={isEnterprise} onClose={() => setCreateGroupExplanation(false)} />}
×
299
        {openIdDialog && (
2!
300
          <Dialog open>
301
            <DialogTitle>Default device identity attribute</DialogTitle>
302
            <DialogContent style={{ overflow: 'hidden' }}>
303
              <Global dialog closeDialog={openSettingsDialog} />
304
            </DialogContent>
305
          </Dialog>
306
        )}
307
        {openPreauth && (
2!
308
          <PreauthDialog
309
            acceptedDevices={acceptedCount}
310
            deviceLimit={deviceLimit}
311
            limitMaxed={limitMaxed}
312
            preauthDevice={authset => dispatch(preauthDevice(authset))}
×
313
            onSubmit={onPreauthSaved}
314
            onCancel={() => setOpenPreauth(false)}
×
315
            setSnackbar={message => dispatch(setSnackbar(message))}
×
316
          />
317
        )}
318
        {showMakeGateway && <MakeGatewayDialog isPreRelease={canPreview} onCancel={toggleMakeGatewayClick} />}
2!
319
      </div>
320
    </>
321
  );
322
};
323

324
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