• 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.38
/src/js/components/devices/authorized-devices.js
1
// Copyright 2015 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, useRef, useState } from 'react';
15
import { connect } from 'react-redux';
16
import { useNavigate } from 'react-router-dom';
17

18
// material ui
19
import { Autorenew as AutorenewIcon, Delete as DeleteIcon, FilterList as FilterListIcon, LockOutlined } from '@mui/icons-material';
20
import { Button, MenuItem, Select } from '@mui/material';
21
import { useTheme } from '@mui/material/styles';
22
import { makeStyles } from 'tss-react/mui';
23

24
import { setSnackbar } from '../../actions/appActions';
25
import { deleteAuthset, setDeviceFilters, setDeviceListState, updateDevicesAuth } from '../../actions/deviceActions';
26
import { getIssueCountsByType } from '../../actions/monitorActions';
27
import { advanceOnboarding } from '../../actions/onboardingActions';
28
import { saveUserSettings, updateUserColumnSettings } from '../../actions/userActions';
29
import { SORTING_OPTIONS, TIMEOUTS } from '../../constants/appConstants';
30
import { ALL_DEVICES, DEVICE_ISSUE_OPTIONS, DEVICE_STATES, UNGROUPED_GROUP } from '../../constants/deviceConstants';
31
import { onboardingSteps } from '../../constants/onboardingConstants';
32
import { duplicateFilter, toggle } from '../../helpers';
33
import {
34
  getAvailableIssueOptionsByType,
35
  getFeatures,
36
  getFilterAttributes,
37
  getIdAttribute,
38
  getMappedDevicesList,
39
  getOnboardingState,
40
  getTenantCapabilities,
41
  getUserCapabilities,
42
  getUserSettings
43
} from '../../selectors';
44
import { useDebounce } from '../../utils/debouncehook';
45
import { getOnboardingComponentFor } from '../../utils/onboardingmanager';
46
import useWindowSize from '../../utils/resizehook';
47
import { clearAllRetryTimers, setRetryTimer } from '../../utils/retrytimer';
48
import Loader from '../common/loader';
49
import { ExpandDevice } from '../helptips/helptooltips';
50
import { defaultHeaders, defaultTextRender, getDeviceIdentityText, routes as states } from './base-devices';
51
import DeviceList, { minCellWidth } from './devicelist';
52
import ColumnCustomizationDialog from './dialogs/custom-columns-dialog';
53
import ExpandedDevice from './expanded-device';
54
import DeviceQuickActions from './widgets/devicequickactions';
55
import Filters from './widgets/filters';
56
import DeviceIssuesSelection from './widgets/issueselection';
57
import ListOptions from './widgets/listoptions';
58

59
const refreshDeviceLength = TIMEOUTS.refreshDefault;
9✔
60

61
const idAttributeTitleMap = {
9✔
62
  id: 'Device ID',
63
  name: 'Name'
64
};
65

66
const headersReducer = (accu, header) => {
9✔
67
  if (header.attribute.scope === accu.column.scope && (header.attribute.name === accu.column.name || header.attribute.alternative === accu.column.name)) {
×
68
    accu.header = { ...accu.header, ...header };
×
69
  }
70
  return accu;
×
71
};
72

73
const useStyles = makeStyles()(theme => ({
9✔
74
  filterCommon: {
75
    borderStyle: 'solid',
76
    borderWidth: 1,
77
    borderRadius: 5,
78
    borderColor: theme.palette.grey[100],
79
    background: theme.palette.background.default,
80
    [`.filter-list > .MuiChip-root`]: {
81
      marginBottom: theme.spacing()
82
    },
83
    [`.filter-list > .MuiChip-root > .MuiChip-label`]: {
84
      whiteSpace: 'normal'
85
    },
86
    ['&.filter-header']: {
87
      overflow: 'hidden',
88
      zIndex: 2
89
    },
90
    ['&.filter-toggle']: {
91
      background: 'transparent',
92
      borderBottomRightRadius: 0,
93
      borderBottomLeftRadius: 0,
94
      borderBottomColor: theme.palette.background.default,
95
      marginBottom: -1
96
    },
97
    ['&.filter-wrapper']: {
98
      padding: 20,
99
      borderTopLeftRadius: 0
100
    }
101
  }
102
}));
103

104
export const getHeaders = (columnSelection = [], currentStateHeaders, idAttribute, openSettingsDialog) => {
9!
105
  const headers = columnSelection.length
578!
106
    ? columnSelection.map(column => {
107
        let header = { ...column, attribute: { ...column }, textRender: defaultTextRender, sortable: true };
×
108
        header = Object.values(defaultHeaders).reduce(headersReducer, { column, header }).header;
×
109
        header = currentStateHeaders.reduce(headersReducer, { column, header }).header;
×
110
        return header;
×
111
      })
112
    : currentStateHeaders;
113
  return [
578✔
114
    {
115
      title: idAttributeTitleMap[idAttribute.attribute] ?? idAttribute.attribute,
659✔
116
      customize: openSettingsDialog,
117
      attribute: { name: idAttribute.attribute, scope: idAttribute.scope },
118
      sortable: true,
119
      textRender: getDeviceIdentityText
120
    },
121
    ...headers,
122
    defaultHeaders.deviceStatus
123
  ];
124
};
125

126
const calculateColumnSelectionSize = (changedColumns, customColumnSizes) =>
9✔
127
  changedColumns.reduce(
1✔
128
    (accu, column) => {
129
      const size = customColumnSizes.find(({ attribute }) => attribute.name === column.key && attribute.scope === column.scope)?.size || minCellWidth;
4✔
130
      accu.columnSizes.push({ attribute: { name: column.key, scope: column.scope }, size });
4✔
131
      accu.selectedAttributes.push({ attribute: column.key, scope: column.scope });
4✔
132
      return accu;
4✔
133
    },
134
    { columnSizes: [], selectedAttributes: [] }
135
  );
136

137
const OnboardingComponent = ({ authorizeRef, deviceListRef, onboardingState, selectedRows }) => {
9✔
138
  let onboardingComponent = null;
10✔
139
  if (deviceListRef.current) {
10✔
140
    const element = deviceListRef.current.querySelector('body .deviceListItem > div');
3✔
141
    const anchor = { left: 200, top: element ? element.offsetTop + element.offsetHeight : 170 };
3!
142
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.DEVICES_ACCEPTED_ONBOARDING, onboardingState, { anchor }, onboardingComponent);
3✔
143
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.DEPLOYMENTS_PAST_COMPLETED, onboardingState, { anchor }, onboardingComponent);
3✔
144
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.DEVICES_PENDING_ONBOARDING, onboardingState, { anchor }, onboardingComponent);
3✔
145
  }
146
  if (selectedRows && authorizeRef.current) {
10!
147
    const anchor = {
×
148
      left: authorizeRef.current.offsetLeft - authorizeRef.current.offsetWidth,
149
      top:
150
        authorizeRef.current.offsetTop +
151
        authorizeRef.current.offsetHeight -
152
        authorizeRef.current.lastElementChild.offsetHeight +
153
        authorizeRef.current.lastElementChild.firstElementChild.offsetHeight * 1.5
154
    };
155
    onboardingComponent = getOnboardingComponentFor(
×
156
      onboardingSteps.DEVICES_PENDING_ACCEPTING_ONBOARDING,
157
      onboardingState,
158
      { place: 'left', anchor },
159
      onboardingComponent
160
    );
161
  }
162
  return onboardingComponent;
10✔
163
};
164

165
export const Authorized = props => {
9✔
166
  const {
167
    acceptedCount,
168
    addDevicesToGroup,
169
    advanceOnboarding,
170
    allCount,
171
    attributes,
172
    availableIssueOptions,
173
    columnSelection,
174
    customColumnSizes,
175
    deleteAuthset,
176
    deviceCount,
177
    deviceListState,
178
    devices,
179
    features,
180
    filters,
181
    getIssueCountsByType,
182
    groupFilters,
183
    highlightHelp,
184
    idAttribute,
185
    limitMaxed,
186
    onboardingState,
187
    onGroupClick,
188
    onGroupRemoval,
189
    onMakeGatewayClick,
190
    onPreauthClick,
191
    openSettingsDialog,
192
    pendingCount,
193
    removeDevicesFromGroup,
194
    saveUserSettings,
195
    selectedGroup,
196
    setDeviceFilters,
197
    setDeviceListState,
198
    settingsInitialized,
199
    showHelptips,
200
    showsDialog,
201
    tenantCapabilities,
202
    updateDevicesAuth,
203
    updateUserColumnSettings,
204
    userCapabilities
205
  } = props;
10✔
206
  const {
207
    refreshTrigger,
208
    selectedId,
209
    selectedIssues = [],
5✔
210
    isLoading: pageLoading,
211
    selection: selectedRows,
212
    sort = {},
×
213
    state: selectedState,
214
    detailsTab: tabSelection
215
  } = deviceListState;
10✔
216
  const { direction: sortDown = SORTING_OPTIONS.desc, key: sortCol } = sort;
10✔
217
  const { canManageDevices } = userCapabilities;
10✔
218
  const { hasMonitor } = tenantCapabilities;
10✔
219
  const currentSelectedState = states[selectedState] ?? states.devices;
10✔
220
  const [columnHeaders, setColumnHeaders] = useState([]);
10✔
221
  const [headerKeys, setHeaderKeys] = useState('');
10✔
222
  const [isInitialized, setIsInitialized] = useState(false);
10✔
223
  const [devicesInitialized, setDevicesInitialized] = useState(!!devices.length);
10✔
224
  const [showFilters, setShowFilters] = useState(false);
10✔
225
  const [showCustomization, setShowCustomization] = useState(false);
10✔
226
  const deviceListRef = useRef();
10✔
227
  const authorizeRef = useRef();
10✔
228
  const timer = useRef();
10✔
229
  const navigate = useNavigate();
10✔
230

231
  // eslint-disable-next-line no-unused-vars
232
  const size = useWindowSize();
10✔
233

234
  const { classes } = useStyles();
10✔
235

236
  useEffect(() => {
10✔
237
    clearAllRetryTimers(setSnackbar);
3✔
238
    if (!filters.length && selectedGroup && groupFilters.length) {
3!
239
      setDeviceFilters(groupFilters);
×
240
    }
241
    return () => {
3✔
242
      clearInterval(timer.current);
3✔
243
      clearAllRetryTimers(setSnackbar);
3✔
244
    };
245
  }, []);
246

247
  useEffect(() => {
10✔
248
    const columnHeaders = getHeaders(columnSelection, currentSelectedState.defaultHeaders, idAttribute, openSettingsDialog);
3✔
249
    setColumnHeaders(columnHeaders);
3✔
250
    setHeaderKeys(columnHeaders.map(({ attribute: { name, scope } }) => `${name}-${scope}`).join('-'));
15✔
251
  }, [columnSelection, selectedState, idAttribute.attribute]);
252

253
  useEffect(() => {
10✔
254
    // only set state after all devices id data retrieved
255
    setIsInitialized(isInitialized => isInitialized || (settingsInitialized && devicesInitialized && pageLoading === false));
4✔
256
    setDevicesInitialized(devicesInitialized => devicesInitialized || pageLoading === false);
4✔
257
  }, [settingsInitialized, devicesInitialized, pageLoading]);
258

259
  useEffect(() => {
10✔
260
    if (onboardingState.complete) {
3!
261
      return;
×
262
    }
263
    if (pendingCount) {
3!
264
      advanceOnboarding(onboardingSteps.DEVICES_PENDING_ONBOARDING_START);
3✔
265
      return;
3✔
266
    }
267
    if (!acceptedCount) {
×
268
      return;
×
269
    }
270
    advanceOnboarding(onboardingSteps.DEVICES_ACCEPTED_ONBOARDING);
×
271

272
    if (acceptedCount < 2) {
×
273
      if (!window.sessionStorage.getItem('pendings-redirect')) {
×
274
        window.sessionStorage.setItem('pendings-redirect', true);
×
275
        onDeviceStateSelectionChange(DEVICE_STATES.accepted);
×
276
      }
277
      setTimeout(() => {
×
278
        const notification = getOnboardingComponentFor(onboardingSteps.DEVICES_ACCEPTED_ONBOARDING_NOTIFICATION, onboardingState, { setSnackbar });
×
279
        !!notification && setSnackbar('open', TIMEOUTS.refreshDefault, '', notification, () => {}, true);
×
280
      }, 400);
281
    }
282
  }, [acceptedCount, allCount, pendingCount, onboardingState.complete]);
283

284
  useEffect(() => {
10✔
285
    setShowFilters(false);
3✔
286
  }, [selectedGroup]);
287

288
  useEffect(() => {
10✔
289
    if (!devicesInitialized) {
4✔
290
      return;
1✔
291
    }
292
    clearInterval(timer.current);
3✔
293
    timer.current = setInterval(
3✔
294
      () =>
295
        setDeviceListState({ refreshTrigger: !refreshTrigger }).catch(err =>
×
296
          setRetryTimer(err, 'devices', `Devices couldn't be loaded.`, refreshDeviceLength, setSnackbar)
×
297
        ),
298
      refreshDeviceLength
299
    );
300
  }, [devicesInitialized, refreshTrigger]);
301

302
  useEffect(() => {
10✔
303
    Object.keys(availableIssueOptions).map(key => getIssueCountsByType(key, { filters, group: selectedGroup, state: selectedState }));
7✔
304
    availableIssueOptions[DEVICE_ISSUE_OPTIONS.authRequests.key] ? getIssueCountsByType(DEVICE_ISSUE_OPTIONS.authRequests.key, { filters: [] }) : undefined;
7!
305
  }, [selectedIssues, availableIssueOptions, selectedState, selectedGroup]);
306

307
  /*
308
   * Devices
309
   */
310
  const devicesToIds = devices => devices.map(device => device.id);
10✔
311

312
  const onRemoveDevicesFromGroup = devices => {
10✔
313
    const deviceIds = devicesToIds(devices);
×
314
    removeDevicesFromGroup(deviceIds);
×
315
    // if devices.length = number on page but < deviceCount
316
    // move page back to pageNO 1
317
    if (devices.length === deviceIds.length) {
×
318
      handlePageChange(1);
×
319
    }
320
  };
321

322
  const onAuthorizationChange = (devices, changedState) => {
10✔
323
    const deviceIds = devicesToIds(devices);
×
324
    return setDeviceListState({ isLoading: true })
×
325
      .then(() => updateDevicesAuth(deviceIds, changedState))
×
326
      .then(() => onSelectionChange([]));
×
327
  };
328

329
  const onDeviceDismiss = devices =>
10✔
330
    setDeviceListState({ isLoading: true })
×
331
      .then(() => {
332
        const deleteRequests = devices.reduce((accu, device) => {
×
333
          if (device.auth_sets?.length) {
×
334
            accu.push(deleteAuthset(device.id, device.auth_sets[0].id));
×
335
          }
336
          return accu;
×
337
        }, []);
338
        return Promise.all(deleteRequests);
×
339
      })
340
      .then(() => onSelectionChange([]));
×
341

342
  const handlePageChange = page => setDeviceListState({ selectedId: undefined, page, refreshTrigger: !refreshTrigger });
10✔
343

344
  const onPageLengthChange = perPage => {
10✔
345
    setDeviceListState({ perPage, page: 1, refreshTrigger: !refreshTrigger });
×
346
  };
347

348
  const refreshDevices = () => setDeviceListState({ refreshTrigger: !refreshTrigger });
10✔
349

350
  const onSortChange = attribute => {
10✔
351
    let changedSortCol = attribute.name;
×
352
    let changedSortDown = sortDown === SORTING_OPTIONS.desc ? SORTING_OPTIONS.asc : SORTING_OPTIONS.desc;
×
353
    if (changedSortCol !== sortCol) {
×
354
      changedSortDown = SORTING_OPTIONS.desc;
×
355
    }
356
    setDeviceListState({
×
357
      sort: { direction: changedSortDown, key: changedSortCol, scope: attribute.scope },
358
      refreshTrigger: !refreshTrigger
359
    });
360
  };
361

362
  const onFilterChange = () => handlePageChange(1);
10✔
363

364
  const onDeviceStateSelectionChange = newState => {
10✔
365
    setDeviceListState({ state: newState, page: 1, refreshTrigger: !refreshTrigger });
×
366
  };
367

368
  const setDetailsTab = detailsTab => setDeviceListState({ detailsTab, setOnly: true });
10✔
369

370
  const onDeviceIssuesSelectionChange = ({ target: { value: selectedIssues } }) => {
10✔
371
    setDeviceListState({ selectedIssues, page: 1, refreshTrigger: !refreshTrigger });
1✔
372
  };
373

374
  const onSelectionChange = (selection = []) => {
10!
375
    if (!onboardingState.complete && selection.length) {
1!
376
      advanceOnboarding(onboardingSteps.DEVICES_PENDING_ACCEPTING_ONBOARDING);
1✔
377
    }
378
    setDeviceListState({ selection, setOnly: true });
1✔
379
  };
380

381
  const onToggleCustomizationClick = () => setShowCustomization(toggle);
10✔
382

383
  const onChangeColumns = (changedColumns, customColumnSizes) => {
10✔
384
    const { columnSizes, selectedAttributes } = calculateColumnSelectionSize(changedColumns, customColumnSizes);
1✔
385
    updateUserColumnSettings(columnSizes);
1✔
386
    saveUserSettings({ columnSelection: changedColumns });
1✔
387
    // we don't need an explicit refresh trigger here, since the selectedAttributes will be different anyway & otherwise the shown list should still be valid
388
    setDeviceListState({ selectedAttributes });
1✔
389
    setShowCustomization(false);
1✔
390
  };
391

392
  const onExpandClick = (device = {}) => {
10!
393
    setSnackbar('');
×
394
    let { attributes = {}, id, status } = device;
×
395
    setDeviceListState({ selectedId: deviceListState.selectedId === id ? undefined : id });
×
396
    if (!onboardingState.complete) {
×
397
      advanceOnboarding(onboardingSteps.DEVICES_PENDING_ONBOARDING);
×
398
      if (status === DEVICE_STATES.accepted && Object.values(attributes).some(value => value)) {
×
399
        advanceOnboarding(onboardingSteps.DEVICES_ACCEPTED_ONBOARDING_NOTIFICATION);
×
400
      }
401
    }
402
  };
403

404
  const onCreateDeploymentClick = devices => navigate(`/deployments?open=true&${devices.map(({ id }) => `deviceId=${id}`).join('&')}`);
10✔
405

406
  const actionCallbacks = {
10✔
407
    onAddDevicesToGroup: addDevicesToGroup,
408
    onAuthorizationChange,
409
    onCreateDeployment: onCreateDeploymentClick,
410
    onDeviceDismiss,
411
    onPromoteGateway: onMakeGatewayClick,
412
    onRemoveDevicesFromGroup
413
  };
414

415
  const listOptionHandlers = [{ key: 'customize', title: 'Customize', onClick: onToggleCustomizationClick }];
10✔
416
  const devicePendingTip = getOnboardingComponentFor(onboardingSteps.DEVICES_PENDING_ONBOARDING_START, onboardingState);
10✔
417

418
  const EmptyState = currentSelectedState.emptyState;
10✔
419

420
  const groupLabel = selectedGroup ? decodeURIComponent(selectedGroup) : ALL_DEVICES;
10✔
421
  const isUngroupedGroup = selectedGroup && selectedGroup === UNGROUPED_GROUP.id;
10✔
422
  const selectedStaticGroup = selectedGroup && !groupFilters.length ? selectedGroup : undefined;
10✔
423

424
  const openedDevice = useDebounce(selectedId, TIMEOUTS.debounceShort);
10✔
425
  return (
10✔
426
    <>
427
      <div className="margin-left-small">
428
        <div className="flexbox">
429
          <h3 className="margin-right">{isUngroupedGroup ? UNGROUPED_GROUP.name : groupLabel}</h3>
10!
430
          <div className="flexbox space-between center-aligned" style={{ flexGrow: 1 }}>
431
            <div className="flexbox">
432
              <DeviceStateSelection onStateChange={onDeviceStateSelectionChange} selectedState={selectedState} states={states} />
433
              {hasMonitor && (
15✔
434
                <DeviceIssuesSelection onChange={onDeviceIssuesSelectionChange} options={Object.values(availableIssueOptions)} selection={selectedIssues} />
435
              )}
436
              {selectedGroup && !isUngroupedGroup && (
14✔
437
                <div className="margin-left muted flexbox centered">
438
                  {!groupFilters.length ? <LockOutlined fontSize="small" /> : <AutorenewIcon fontSize="small" />}
2!
439
                  <span>{!groupFilters.length ? 'Static' : 'Dynamic'}</span>
2!
440
                </div>
441
              )}
442
            </div>
443
            {canManageDevices && selectedGroup && !isUngroupedGroup && (
24✔
444
              <Button onClick={onGroupRemoval} startIcon={<DeleteIcon />}>
445
                Remove group
446
              </Button>
447
            )}
448
          </div>
449
        </div>
450
        <div className="flexbox space-between">
451
          {!isUngroupedGroup && (
20✔
452
            <div className={`flexbox centered filter-header ${showFilters ? `${classes.filterCommon} filter-toggle` : ''}`}>
10!
453
              <Button
454
                color="secondary"
455
                disableRipple
456
                onClick={() => setShowFilters(toggle)}
×
457
                startIcon={<FilterListIcon />}
458
                style={{ backgroundColor: 'transparent' }}
459
              >
460
                {filters.length > 0 ? `Filters (${filters.length})` : 'Filters'}
10!
461
              </Button>
462
            </div>
463
          )}
464
          <ListOptions options={listOptionHandlers} title="Table options" />
465
        </div>
466
        <Filters
467
          className={classes.filterCommon}
468
          onFilterChange={onFilterChange}
469
          onGroupClick={onGroupClick}
470
          isModification={!!groupFilters.length}
471
          open={showFilters}
472
        />
473
      </div>
474
      <Loader show={!isInitialized} />
475
      {isInitialized ? (
10✔
476
        devices.length > 0 ? (
6✔
477
          <div className="padding-bottom" ref={deviceListRef}>
478
            <DeviceList
479
              {...props}
480
              columnHeaders={columnHeaders}
481
              customColumnSizes={customColumnSizes}
482
              headerKeys={headerKeys}
483
              onChangeRowsPerPage={onPageLengthChange}
484
              onExpandClick={onExpandClick}
485
              onPageChange={handlePageChange}
486
              onResizeColumns={updateUserColumnSettings}
487
              onSelect={onSelectionChange}
488
              onSort={onSortChange}
489
              pageLoading={pageLoading}
490
              pageTotal={deviceCount}
491
            />
492
            {showHelptips && <ExpandDevice />}
10✔
493
          </div>
494
        ) : (
495
          <>
496
            {devicePendingTip && !showsDialog ? (
2!
497
              devicePendingTip
498
            ) : (
499
              <EmptyState
500
                allCount={allCount}
501
                canManageDevices={canManageDevices}
502
                filters={filters}
503
                highlightHelp={highlightHelp}
504
                limitMaxed={limitMaxed}
505
                onClick={onPreauthClick}
506
              />
507
            )}
508
          </>
509
        )
510
      ) : (
511
        <div />
512
      )}
513
      <ExpandedDevice
514
        actionCallbacks={actionCallbacks}
515
        deviceId={openedDevice}
516
        onClose={() => setDeviceListState({ selectedId: undefined })}
×
517
        refreshDevices={refreshDevices}
518
        setDetailsTab={setDetailsTab}
519
        tabSelection={tabSelection}
520
      />
521
      {!selectedId && (
20✔
522
        <OnboardingComponent authorizeRef={authorizeRef} deviceListRef={deviceListRef} onboardingState={onboardingState} selectedRows={selectedRows} />
523
      )}
524
      {canManageDevices && !!selectedRows.length && (
20!
525
        <DeviceQuickActions
526
          actionCallbacks={actionCallbacks}
527
          devices={devices}
528
          features={features}
529
          selectedGroup={selectedStaticGroup}
530
          selectedRows={selectedRows}
531
          ref={authorizeRef}
532
          tenantCapabilities={tenantCapabilities}
533
          userCapabilities={userCapabilities}
534
        />
535
      )}
536
      <ColumnCustomizationDialog
537
        attributes={attributes}
538
        columnHeaders={columnHeaders}
539
        customColumnSizes={customColumnSizes}
540
        idAttribute={idAttribute}
541
        open={showCustomization}
542
        onCancel={onToggleCustomizationClick}
543
        onSubmit={onChangeColumns}
544
      />
545
    </>
546
  );
547
};
548

549
const actionCreators = {
9✔
550
  advanceOnboarding,
551
  deleteAuthset,
552
  getIssueCountsByType,
553
  saveUserSettings,
554
  setDeviceFilters,
555
  setDeviceListState,
556
  setSnackbar,
557
  updateDevicesAuth,
558
  updateUserColumnSettings
559
};
560

561
const mapStateToProps = state => {
9✔
562
  let devices = getMappedDevicesList(state, 'deviceList');
2✔
563
  let deviceCount = state.devices.deviceList.total;
2✔
564
  let selectedGroup;
565
  let groupFilters = [];
2✔
566
  if (state.devices.groups.selectedGroup && state.devices.groups.byId[state.devices.groups.selectedGroup]) {
2✔
567
    selectedGroup = state.devices.groups.selectedGroup;
1✔
568
    groupFilters = state.devices.groups.byId[selectedGroup].filters || [];
1!
569
  }
570
  const { columnSelection = [] } = getUserSettings(state);
2!
571
  return {
2✔
572
    attributes: getFilterAttributes(state),
573
    acceptedCount: state.devices.byStatus.accepted.total || 0,
3✔
574
    allCount: state.devices.byStatus.accepted.total + state.devices.byStatus.rejected.total || 0,
3✔
575
    availableIssueOptions: getAvailableIssueOptionsByType(state),
576
    columnSelection,
577
    customColumnSizes: state.users.customColumns,
578
    devices,
579
    deviceListState: state.devices.deviceList,
580
    deviceCount,
581
    features: getFeatures(state),
582
    filters: state.devices.filters || [],
2!
583
    groupFilters,
584
    idAttribute: getIdAttribute(state),
585
    onboardingState: getOnboardingState(state),
586
    pendingCount: state.devices.byStatus.pending.total || 0,
2!
587
    selectedGroup,
588
    settingsInitialized: state.users.settingsInitialized,
589
    showHelptips: state.users.showHelptips,
590
    tenantCapabilities: getTenantCapabilities(state),
591
    userCapabilities: getUserCapabilities(state)
592
  };
593
};
594

595
export default connect(mapStateToProps, actionCreators)(Authorized);
596

597
export const DeviceStateSelection = ({ onStateChange, selectedState = '', states }) => {
9✔
598
  const theme = useTheme();
12✔
599
  const availableStates = useMemo(() => Object.values(states).filter(duplicateFilter), [states]);
12✔
600

601
  return (
12✔
602
    <div className="flexbox centered">
603
      Status:
604
      <Select
605
        disableUnderline
606
        onChange={e => onStateChange(e.target.value)}
1✔
607
        value={selectedState}
608
        style={{ fontSize: 13, marginLeft: theme.spacing(), marginTop: 2 }}
609
      >
610
        {availableStates.map(state => (
611
          <MenuItem key={state.key} value={state.key}>
62✔
612
            {state.title()}
613
          </MenuItem>
614
        ))}
615
      </Select>
616
    </div>
617
  );
618
};
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