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

mendersoftware / gui / 1057188406

01 Nov 2023 04:24AM UTC coverage: 82.824% (-17.1%) from 99.964%
1057188406

Pull #4134

gitlab-ci

web-flow
chore: Bump uuid from 9.0.0 to 9.0.1

Bumps [uuid](https://github.com/uuidjs/uuid) from 9.0.0 to 9.0.1.
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v9.0.0...v9.0.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4134: chore: Bump uuid from 9.0.0 to 9.0.1

4349 of 6284 branches covered (0.0%)

8313 of 10037 relevant lines covered (82.82%)

200.97 hits per line

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

81.67
/src/js/components/devices/widgets/devicequickactions.js
1
// Copyright 2021 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 { useSelector } from 'react-redux';
16

17
import {
18
  AddCircle as AddCircleIcon,
19
  CheckCircle as CheckCircleIcon,
20
  HeightOutlined as HeightOutlinedIcon,
21
  HighlightOffOutlined as HighlightOffOutlinedIcon,
22
  RemoveCircleOutline as RemoveCircleOutlineIcon,
23
  Replay as ReplayIcon
24
} from '@mui/icons-material';
25
import { ClickAwayListener, SpeedDial, SpeedDialAction, SpeedDialIcon } from '@mui/material';
26
import { speedDialActionClasses } from '@mui/material/SpeedDialAction';
27
import { makeStyles } from 'tss-react/mui';
28

29
import { mdiTrashCanOutline as TrashCan } from '@mdi/js';
30
import pluralize from 'pluralize';
31

32
import GatewayIcon from '../../../../assets/img/gateway.svg';
33
import { TIMEOUTS } from '../../../constants/appConstants';
34
import { DEVICE_STATES, UNGROUPED_GROUP } from '../../../constants/deviceConstants';
35
import { onboardingSteps } from '../../../constants/onboardingConstants';
36
import { stringToBoolean, toggle } from '../../../helpers';
37
import { getDeviceById, getFeatures, getMappedDevicesList, getOnboardingState, getTenantCapabilities, getUserCapabilities } from '../../../selectors';
38
import { getOnboardingComponentFor } from '../../../utils/onboardingmanager';
39
import MaterialDesignIcon from '../../common/materialdesignicon';
40

41
const defaultActions = {
10✔
42
  accept: {
43
    icon: <CheckCircleIcon className="green" />,
44
    key: 'accept',
45
    title: pluralized => `Accept ${pluralized}`,
1✔
46
    action: ({ onAuthorizationChange, selection }) => onAuthorizationChange(selection, DEVICE_STATES.accepted),
×
47
    checkRelevance: ({ device, userCapabilities: { canWriteDevices } }) =>
48
      canWriteDevices && [DEVICE_STATES.pending, DEVICE_STATES.rejected].includes(device.status)
19✔
49
  },
50
  dismiss: {
51
    icon: <RemoveCircleOutlineIcon className="red" />,
52
    key: 'dismiss',
53
    title: pluralized => `Dismiss ${pluralized}`,
14✔
54
    action: ({ onDeviceDismiss, selection }) => onDeviceDismiss(selection),
×
55
    checkRelevance: ({ device, userCapabilities: { canWriteDevices } }) =>
56
      canWriteDevices && [DEVICE_STATES.accepted, DEVICE_STATES.pending, DEVICE_STATES.preauth, DEVICE_STATES.rejected, 'noauth'].includes(device.status)
31✔
57
  },
58
  reject: {
59
    icon: <HighlightOffOutlinedIcon className="red" />,
60
    key: 'reject',
61
    title: pluralized => `Reject ${pluralized}`,
14✔
62
    action: ({ onAuthorizationChange, selection }) => onAuthorizationChange(selection, DEVICE_STATES.rejected),
×
63
    checkRelevance: ({ device, userCapabilities: { canWriteDevices } }) =>
64
      canWriteDevices && [DEVICE_STATES.accepted, DEVICE_STATES.pending].includes(device.status)
31✔
65
  },
66
  addToGroup: {
67
    icon: <AddCircleIcon className="green" />,
68
    key: 'group-add',
69
    title: pluralized => `Add selected ${pluralized} to a group`,
13✔
70
    action: ({ onAddDevicesToGroup, selection }) => onAddDevicesToGroup(selection),
×
71
    checkRelevance: ({ selectedGroup, userCapabilities: { canWriteDevices } }) => canWriteDevices && !selectedGroup
31✔
72
  },
73
  moveToGroup: {
74
    icon: <HeightOutlinedIcon className="rotated ninety" />,
75
    key: 'group-change',
76
    title: pluralized => `Move selected ${pluralized} to another group`,
1✔
77
    action: ({ onAddDevicesToGroup, selection }) => onAddDevicesToGroup(selection),
×
78
    checkRelevance: ({ selectedGroup, userCapabilities: { canWriteDevices } }) => canWriteDevices && !!selectedGroup
19✔
79
  },
80
  removeFromGroup: {
81
    icon: <MaterialDesignIcon path={TrashCan} />,
82
    key: 'group-remove',
83
    title: pluralized => `Remove selected ${pluralized} from this group`,
1✔
84
    action: ({ onRemoveDevicesFromGroup, selection }) => onRemoveDevicesFromGroup(selection),
×
85
    checkRelevance: ({ selectedGroup, userCapabilities: { canWriteDevices } }) => canWriteDevices && selectedGroup && selectedGroup !== UNGROUPED_GROUP.id
19✔
86
  },
87
  promoteToGateway: {
88
    icon: <GatewayIcon style={{ width: 20 }} />,
89
    key: 'promote-to-gateway',
90
    title: () => 'Promote to gateway',
×
91
    action: ({ onPromoteGateway, selection }) => onPromoteGateway(selection),
×
92
    checkRelevance: ({ device, features, tenantCapabilities: { isEnterprise } }) =>
93
      features.isHosted && isEnterprise && !stringToBoolean(device.attributes?.mender_is_gateway) && device.status === DEVICE_STATES.accepted
19!
94
  },
95
  createDeployment: {
96
    icon: <ReplayIcon />,
97
    key: 'create-deployment',
98
    title: (pluralized, count) => `Create deployment for ${pluralize('this', count)} ${pluralized}`,
13✔
99
    action: ({ onCreateDeployment, selection }) => onCreateDeployment(selection),
×
100
    checkRelevance: ({ device, userCapabilities: { canDeploy, canReadReleases } }) =>
101
      canDeploy && canReadReleases && device && device.status === DEVICE_STATES.accepted
31✔
102
  }
103
};
104

105
const useStyles = makeStyles()(theme => ({
10✔
106
  container: {
107
    position: 'fixed',
108
    bottom: theme.spacing(6.5),
109
    right: theme.spacing(6.5),
110
    zIndex: 10,
111
    minWidth: 400,
112
    pointerEvents: 'none',
113
    [`.${speedDialActionClasses.staticTooltipLabel}`]: {
114
      minWidth: 'max-content'
115
    }
116
  },
117
  fab: { margin: theme.spacing(2) },
118
  innerContainer: {
119
    display: 'flex',
120
    alignItems: 'flex-end',
121
    justifyContent: 'flex-end'
122
  },
123
  label: {
124
    marginRight: theme.spacing(2),
125
    marginBottom: theme.spacing(4)
126
  }
127
}));
128

129
export const DeviceQuickActions = ({ actionCallbacks, deviceId, selectedGroup }) => {
10✔
130
  const [showActions, setShowActions] = useState(false);
19✔
131
  const features = useSelector(getFeatures);
19✔
132
  const tenantCapabilities = useSelector(getTenantCapabilities);
19✔
133
  const userCapabilities = useSelector(getUserCapabilities);
19✔
134
  const { selection: selectedRows } = useSelector(state => state.devices.deviceList);
35✔
135
  const singleDevice = useSelector(state => getDeviceById(state, deviceId));
35✔
136
  const devices = useSelector(state => getMappedDevicesList(state, 'deviceList'));
35✔
137
  const { classes } = useStyles();
19✔
138
  const deployActionRef = useRef();
19✔
139
  const onboardingState = useSelector(getOnboardingState);
19✔
140
  const [isInitialized, setIsInitialized] = useState(false);
19✔
141
  const timer = useRef();
19✔
142

143
  const handleShowActions = () => {
19✔
144
    setShowActions(!showActions);
×
145
  };
146

147
  const handleClickAway = () => {
19✔
148
    setShowActions(false);
6✔
149
  };
150

151
  useEffect(() => {
19✔
152
    clearTimeout(timer.current);
3✔
153
    timer.current = setTimeout(() => setIsInitialized(toggle), TIMEOUTS.debounceDefault);
3✔
154
    return () => {
3✔
155
      clearTimeout(timer.current);
3✔
156
    };
157
  }, []);
158

159
  const selectedDevices = deviceId ? [singleDevice] : selectedRows.map(row => devices[row]);
35✔
160
  const actions = Object.values(defaultActions).reduce((accu, action) => {
19✔
161
    if (selectedDevices.every(device => device && action.checkRelevance({ device, features, selectedGroup, tenantCapabilities, userCapabilities }))) {
220✔
162
      accu.push(action);
57✔
163
    }
164
    return accu;
152✔
165
  }, []);
166

167
  const pluralized = pluralize('devices', selectedDevices.length);
19✔
168

169
  let onboardingComponent;
170
  if (deployActionRef.current && isInitialized) {
19✔
171
    const anchor = {
2✔
172
      left: deployActionRef.current.firstElementChild.offsetLeft - 15,
173
      top: deployActionRef.current.offsetTop + deployActionRef.current.firstElementChild.offsetTop + deployActionRef.current.firstElementChild.offsetHeight / 2
174
    };
175
    onboardingComponent = getOnboardingComponentFor(
2✔
176
      onboardingSteps.DEVICES_DEPLOY_RELEASE_ONBOARDING,
177
      onboardingState,
178
      { anchor, place: 'left' },
179
      onboardingComponent
180
    );
181
  }
182
  return (
19✔
183
    <div className={classes.container}>
184
      <div className="relative">
185
        <div className={classes.innerContainer} ref={deployActionRef}>
186
          <div className={classes.label}>{deviceId ? 'Device actions' : `${selectedDevices.length} ${pluralized} selected`}</div>
19✔
187
          <ClickAwayListener onClickAway={handleClickAway}>
188
            <SpeedDial className={classes.fab} ariaLabel="device-actions" icon={<SpeedDialIcon />} onClick={handleShowActions} open={Boolean(showActions)}>
189
              {actions.map(action => (
190
                <SpeedDialAction
57✔
191
                  key={action.key}
192
                  aria-label={action.key}
193
                  icon={action.icon}
194
                  tooltipTitle={action.title(pluralized, selectedDevices.length)}
195
                  tooltipOpen
196
                  onClick={() => action.action({ ...actionCallbacks, selection: selectedDevices })}
×
197
                />
198
              ))}
199
            </SpeedDial>
200
          </ClickAwayListener>
201
        </div>
202
        {onboardingComponent}
203
      </div>
204
    </div>
205
  );
206
};
207

208
export default DeviceQuickActions;
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