• 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

56.54
/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 { connect } 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
  getAllDeviceCounts,
28
  preauthDevice,
29
  removeDevicesFromGroup,
30
  removeDynamicGroup,
31
  removeStaticGroup,
32
  selectGroup,
33
  setDeviceFilters,
34
  setDeviceListState,
35
  updateDynamicGroup
36
} from '../../actions/deviceActions';
37
import { setShowConnectingDialog } from '../../actions/userActions';
38
import { SORTING_OPTIONS, TIMEOUTS } from '../../constants/appConstants';
39
import { DEVICE_FILTERING_OPTIONS, DEVICE_ISSUE_OPTIONS, DEVICE_STATES, emptyFilter } from '../../constants/deviceConstants';
40
import { toggle, versionCompare } from '../../helpers';
41
import { getDocsVersion, getFeatures, getGroups as getGroupsSelector, getLimitMaxed, getTenantCapabilities, getUserCapabilities } from '../../selectors';
42
import { useLocationParams } from '../../utils/liststatehook';
43
import Global from '../settings/global';
44
import AuthorizedDevices from './authorized-devices';
45
import DeviceStatusNotification from './devicestatusnotification';
46
import MakeGatewayDialog from './dialogs/make-gateway-dialog';
47
import PreauthDialog, { DeviceLimitWarning } from './dialogs/preauth-dialog';
48
import CreateGroup from './group-management/create-group';
49
import CreateGroupExplainer from './group-management/create-group-explainer';
50
import RemoveGroup from './group-management/remove-group';
51
import Groups from './groups';
52
import DeviceAdditionWidget from './widgets/deviceadditionwidget';
53

54
const refreshLength = TIMEOUTS.refreshDefault;
4✔
55

56
export const DeviceGroups = ({
4✔
57
  acceptedCount,
58
  addDynamicGroup,
59
  addStaticGroup,
60
  authRequestCount,
61
  canPreview,
62
  canManageDevices,
63
  deviceLimit,
64
  deviceListState,
65
  docsVersion,
66
  features,
67
  filteringAttributes,
68
  filters,
69
  getAllDeviceCounts,
70
  groupCount,
71
  groupFilters,
72
  groups,
73
  groupsByType,
74
  hasReporting,
75
  limitMaxed,
76
  pendingCount,
77
  preauthDevice,
78
  removeDevicesFromGroup,
79
  removeDynamicGroup,
80
  removeStaticGroup,
81
  selectedGroup,
82
  selectGroup,
83
  setDeviceFilters,
84
  setDeviceListState,
85
  setShowConnectingDialog,
86
  setOfflineThreshold,
87
  setSnackbar,
88
  showDeviceConnectionDialog,
89
  showHelptips,
90
  tenantCapabilities,
91
  updateDynamicGroup
92
}) => {
93
  const [createGroupExplanation, setCreateGroupExplanation] = useState(false);
1✔
94
  const [fromFilters, setFromFilters] = useState(false);
1✔
95
  const [modifyGroupDialog, setModifyGroupDialog] = useState(false);
1✔
96
  const [openIdDialog, setOpenIdDialog] = useState(false);
1✔
97
  const [openPreauth, setOpenPreauth] = useState(false);
1✔
98
  const [showMakeGateway, setShowMakeGateway] = useState(false);
1✔
99
  const [removeGroup, setRemoveGroup] = useState(false);
1✔
100
  const [tmpDevices, setTmpDevices] = useState([]);
1✔
101
  const deviceTimer = useRef();
1✔
102
  const { isEnterprise } = tenantCapabilities;
1✔
103
  const { status: statusParam } = useParams();
1✔
104

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

111
  const { refreshTrigger, selectedId, state: selectedState } = deviceListState;
1✔
112

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

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

144
  useEffect(() => {
1✔
145
    const { groupName, filters = [], id = [], ...remainder } = locationParams;
1!
146
    const { hasFullFiltering } = tenantCapabilities;
1✔
147
    if (groupName) {
1!
148
      selectGroup(groupName, filters);
×
149
    } else if (filters.length) {
1!
150
      setDeviceFilters(filters);
×
151
    }
152
    const state = statusParam && Object.values(DEVICE_STATES).some(state => state === statusParam) ? statusParam : selectedState;
1!
153
    let listState = { ...remainder, state, refreshTrigger: !refreshTrigger };
1✔
154
    if (id.length === 1 && Boolean(locationParams.open)) {
1!
155
      listState.selectedId = id[0];
×
156
    } else if (id.length && hasFullFiltering) {
1!
157
      setDeviceFilters([...filters, { ...emptyFilter, key: 'id', operator: DEVICE_FILTERING_OPTIONS.$in.key, value: id }]);
×
158
    }
159
    setDeviceListState(listState);
1✔
160
    clearInterval(deviceTimer.current);
1✔
161
    deviceTimer.current = setInterval(getAllDeviceCounts, refreshLength);
1✔
162
    setOfflineThreshold();
1✔
163
    return () => {
1✔
164
      clearInterval(deviceTimer.current);
1✔
165
    };
166
  }, []);
167

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

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

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

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

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

212
  const openSettingsDialog = e => {
1✔
213
    e.preventDefault();
×
214
    setOpenIdDialog(toggle);
×
215
  };
216

217
  const onCreateGroupClose = () => {
1✔
218
    setModifyGroupDialog(false);
×
219
    setFromFilters(false);
×
220
    setTmpDevices([]);
×
221
  };
222

223
  const onPreauthSaved = addMore => {
1✔
224
    setOpenPreauth(!addMore);
×
225
    setDeviceListState({ page: 1, refreshTrigger: !refreshTrigger });
×
226
  };
227

228
  const onShowDeviceStateClick = state => {
1✔
229
    selectGroup();
×
230
    setDeviceListState({ state });
×
231
  };
232

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

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

243
  const toggleGroupRemoval = () => setRemoveGroup(toggle);
1✔
244

245
  const toggleMakeGatewayClick = () => setShowMakeGateway(toggle);
1✔
246

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

335
const actionCreators = {
4✔
336
  addDynamicGroup,
337
  addStaticGroup,
338
  getAllDeviceCounts,
339
  preauthDevice,
340
  removeDevicesFromGroup,
341
  removeDynamicGroup,
342
  removeStaticGroup,
343
  selectGroup,
344
  setDeviceFilters,
345
  setDeviceListState,
346
  setOfflineThreshold,
347
  setShowConnectingDialog,
348
  setSnackbar,
349
  updateDynamicGroup
350
};
351

352
const mapStateToProps = state => {
4✔
353
  let groupCount = state.devices.byStatus.accepted.total;
1✔
354
  let selectedGroup;
355
  let groupFilters = [];
1✔
356
  if (state.devices.groups.selectedGroup && state.devices.groups.byId[state.devices.groups.selectedGroup]) {
1!
357
    selectedGroup = state.devices.groups.selectedGroup;
1✔
358
    groupCount = state.devices.groups.byId[selectedGroup].total;
1✔
359
    groupFilters = state.devices.groups.byId[selectedGroup].filters || [];
1!
360
  }
361
  const filteringAttributes = { ...state.devices.filteringAttributes, identityAttributes: [...state.devices.filteringAttributes.identityAttributes, 'id'] };
1✔
362
  const { canManageDevices } = getUserCapabilities(state);
1✔
363
  const tenantCapabilities = getTenantCapabilities(state);
1✔
364
  const { groupNames, ...groupsByType } = getGroupsSelector(state);
1✔
365
  return {
1✔
366
    acceptedCount: state.devices.byStatus.accepted.total || 0,
1!
367
    authRequestCount: state.monitor.issueCounts.byType[DEVICE_ISSUE_OPTIONS.authRequests.key].total,
368
    canPreview: versionCompare(state.app.versionInformation.Integration, 'next') > -1,
369
    canManageDevices,
370
    deviceLimit: state.devices.limit,
371
    deviceListState: state.devices.deviceList,
372
    docsVersion: getDocsVersion(state),
373
    features: getFeatures(state),
374
    filteringAttributes,
375
    filters: state.devices.filters || [],
1!
376
    groups: groupNames,
377
    groupsByType,
378
    groupCount,
379
    groupFilters,
380
    hasReporting: state.app.features.hasReporting,
381
    limitMaxed: getLimitMaxed(state),
382
    pendingCount: state.devices.byStatus.pending.total || 0,
1!
383
    selectedGroup,
384
    showDeviceConnectionDialog: state.users.showConnectDeviceDialog,
385
    showHelptips: state.users.showHelptips,
386
    tenantCapabilities
387
  };
388
};
389

390
export default connect(mapStateToProps, actionCreators)(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