• 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

71.02
/src/js/components/deployments/deployment-wizard/softwaredevices.js
1
// Copyright 2019 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, useState } from 'react';
15
import { Link } from 'react-router-dom';
16

17
import { ErrorOutline as ErrorOutlineIcon } from '@mui/icons-material';
18
import { Autocomplete, TextField, Tooltip } from '@mui/material';
19
import { makeStyles } from 'tss-react/mui';
20

21
import pluralize from 'pluralize';
22
import isUUID from 'validator/lib/isUUID';
23

24
import { DEPLOYMENT_TYPES } from '../../../constants/deploymentConstants';
25
import { ALL_DEVICES } from '../../../constants/deviceConstants';
26
import { stringToBoolean } from '../../../helpers';
27
import useWindowSize from '../../../utils/resizehook';
28
import AsyncAutocomplete from '../../common/asyncautocomplete';
29
import InfoHint from '../../common/info-hint';
30
import InfoText from '../../common/infotext';
31
import { getDeviceIdentityText } from '../../devices/base-devices';
32

33
const useStyles = makeStyles()(theme => ({
17✔
34
  infoStyle: {
35
    minWidth: 400,
36
    borderBottom: 'none'
37
  },
38
  selection: { minWidth: 'min-content', maxWidth: theme.spacing(50), minHeight: 96, marginBottom: theme.spacing(2) }
39
}));
40

41
const hardCodedStyle = {
17✔
42
  textField: {
43
    minWidth: 400
44
  }
45
};
46

47
export const getDevicesLink = ({ devices, group, hasFullFiltering, name }) => {
17✔
48
  let devicesLink = '/devices';
21✔
49
  if (devices.length && (!name || isUUID(name))) {
21!
50
    devicesLink = `${devicesLink}?id=${devices[0].id}`;
×
51
    if (hasFullFiltering) {
×
52
      devicesLink = `/devices?${devices.map(({ id }) => `id=${id}`).join('&')}`;
×
53
    }
54
    if (devices.length === 1) {
×
55
      const { systemDeviceIds = [] } = devices[0];
×
56
      devicesLink = `${devicesLink}${systemDeviceIds.map(id => `&id=${id}`).join('')}`;
×
57
    }
58
  } else if (group && group !== ALL_DEVICES) {
21✔
59
    devicesLink = `${devicesLink}?inventory=group:eq:${group}`;
1✔
60
  }
61
  return devicesLink;
21✔
62
};
63

64
export const getDeploymentTargetText = ({ deployment, devicesById, idAttribute }) => {
17✔
65
  const { devices = {}, group = '', name = '', type = DEPLOYMENT_TYPES.software } = deployment;
4,197✔
66
  let deviceList = Array.isArray(devices) ? devices : Object.values(devices);
4,197✔
67
  if (isUUID(name) && devicesById[name]) {
4,197!
68
    deviceList = [devicesById[name]];
×
69
  }
70
  if (type !== DEPLOYMENT_TYPES.configuration && (!deviceList.length || group || (deployment.name !== undefined && !isUUID(name)))) {
4,197!
71
    return (group || name) ?? '';
4,197!
72
  }
73
  return deviceList.map(device => getDeviceIdentityText({ device, idAttribute }) ?? device?.id).join(', ') || name;
×
74
};
75

76
export const ReleasesWarning = ({ lacksReleases }) => (
17✔
77
  <div className="flexbox center-aligned">
67✔
78
    <ErrorOutlineIcon fontSize="small" style={{ marginRight: 4, top: 4, color: 'rgb(171, 16, 0)' }} />
79
    <InfoText>
80
      There are no {lacksReleases ? 'compatible ' : ''}artifacts available.{lacksReleases ? <br /> : ' '}
134✔
81
      <Link to="/releases">Upload one to the repository</Link> to get started.
82
    </InfoText>
83
  </div>
84
);
85

86
export const Devices = ({
17✔
87
  deploymentObject,
88
  getSystemDevices,
89
  groupRef,
90
  groupNames,
91
  hasDevices,
92
  hasDynamicGroups,
93
  hasFullFiltering,
94
  hasPending,
95
  idAttribute,
96
  setDeploymentSettings
97
}) => {
98
  const { classes } = useStyles();
241✔
99
  // eslint-disable-next-line no-unused-vars
100
  const size = useWindowSize();
241✔
101

102
  const { deploymentDeviceCount = 0, devices = [], group = null } = deploymentObject;
241✔
103
  const device = devices.length === 1 ? devices[0] : {};
241!
104

105
  useEffect(() => {
241✔
106
    const { attributes = {} } = device;
6✔
107
    const { mender_is_gateway } = attributes;
6✔
108
    if (!device.id || !stringToBoolean(mender_is_gateway)) {
6!
109
      return;
6✔
110
    }
111
    getSystemDevices(device.id, { perPage: 500 });
×
112
  }, [device.id, device.attributes?.mender_is_gateway]);
113

114
  const deploymentSettingsUpdate = (e, value, reason) => {
241✔
115
    let update = { group: value };
2✔
116
    if (reason === 'clear') {
2!
117
      update = { ...update, deploymentDeviceCount: 0, devices: [] };
×
118
    }
119
    setDeploymentSettings(update);
2✔
120
  };
121

122
  const { deviceText, devicesLink, targetDeviceCount, targetDevicesText } = useMemo(() => {
241✔
123
    const devicesLink = getDevicesLink({ devices, group, hasFullFiltering });
16✔
124
    let deviceText = getDeploymentTargetText({ deployment: deploymentObject, idAttribute });
16✔
125
    let targetDeviceCount = deploymentDeviceCount;
16✔
126
    let targetDevicesText = `${deploymentDeviceCount} ${pluralize('devices', deploymentDeviceCount)}`;
16✔
127
    if (device?.id) {
16!
128
      const { attributes = {}, systemDeviceIds = [] } = device;
×
129
      const { mender_is_gateway } = attributes;
×
130
      deviceText = `${getDeviceIdentityText({ device, idAttribute })}${stringToBoolean(mender_is_gateway) ? ' (System)' : ''}`;
×
131
      // here we hope the number of systemDeviceIds doesn't exceed the queried 500 and add the gateway device
132
      targetDeviceCount = systemDeviceIds.length + 1;
×
133
    } else if (group) {
16✔
134
      deviceText = '';
4✔
135
      targetDevicesText = 'All devices';
4✔
136
      targetDeviceCount = 2;
4✔
137
      if (group !== ALL_DEVICES) {
4!
138
        targetDevicesText = `${targetDevicesText} in this group`;
×
139
        targetDeviceCount = deploymentDeviceCount;
×
140
      }
141
    }
142
    return { deviceText, devicesLink, targetDeviceCount, targetDevicesText };
16✔
143
  }, [devices, group, idAttribute, deploymentDeviceCount]);
144

145
  return (
241✔
146
    <>
147
      <h4 className="margin-bottom-none margin-top-none">Select a device group to target</h4>
148
      <div ref={groupRef} className={classes.selection}>
149
        {deviceText ? (
241!
150
          <TextField value={deviceText} label={pluralize('device', devices.length)} disabled={true} className={classes.infoStyle} />
151
        ) : (
152
          <div>
153
            <Autocomplete
154
              id="deployment-device-group-selection"
155
              autoSelect
156
              autoHighlight
157
              filterSelectedOptions
158
              handleHomeEndKeys
159
              disabled={!(hasDevices || hasDynamicGroups)}
243✔
160
              options={groupNames}
161
              onChange={deploymentSettingsUpdate}
162
              renderInput={params => (
163
                <TextField {...params} placeholder="Select a device group" InputProps={{ ...params.InputProps }} className={classes.textField} />
250✔
164
              )}
165
              value={group}
166
            />
167
            {!(hasDevices || hasDynamicGroups) && (
484!
168
              <InfoText style={{ marginTop: '10px' }}>
169
                <ErrorOutlineIcon style={{ marginRight: '4px', fontSize: '18px', top: '4px', color: 'rgb(171, 16, 0)', position: 'relative' }} />
170
                There are no connected devices.{' '}
171
                {hasPending ? (
×
172
                  <span>
173
                    <Link to="/devices/pending">Accept pending devices</Link> to get started.
174
                  </span>
175
                ) : (
176
                  <span>
177
                    <Link to="/help/get-started">Read the help pages</Link> for help with connecting devices.
178
                  </span>
179
                )}
180
              </InfoText>
181
            )}
182
          </div>
183
        )}
184
        {!!targetDeviceCount && (
307✔
185
          <InfoText>
186
            {targetDevicesText} will be targeted. <Link to={devicesLink}>View the {pluralize('devices', targetDeviceCount)}</Link>
187
          </InfoText>
188
        )}
189
      </div>
190
    </>
191
  );
192
};
193

194
export const Software = ({
17✔
195
  commonClasses,
196
  deploymentObject,
197
  getReleases,
198
  releaseRef,
199
  releases,
200
  releasesById,
201
  releaseSelectionLocked,
202
  setDeploymentSettings
203
}) => {
204
  const [isLoadingReleases, setIsLoadingReleases] = useState(!releases.length);
241✔
205
  const { classes } = useStyles();
241✔
206
  const { devices = [], release: deploymentRelease = null } = deploymentObject;
241✔
207
  const device = devices.length ? devices[0] : undefined;
241!
208

209
  useEffect(() => {
241✔
210
    setIsLoadingReleases(!releases.length);
9✔
211
  }, [releases.length]);
212

213
  const releaseItems = useMemo(() => {
241✔
214
    let releaseItems = releases.map(rel => releasesById[rel]);
302✔
215
    if (device && device.attributes) {
9!
216
      // If single device, don't show incompatible releases
217
      releaseItems = releaseItems.filter(rel => rel.device_types_compatible.some(type => device.attributes.device_type.includes(type)));
×
218
    }
219
    return releaseItems;
9✔
220
  }, [releases, device]);
221

222
  const onReleaseSelectionChange = release => {
241✔
223
    if (release !== deploymentObject.release) {
2!
224
      setDeploymentSettings({ release });
2✔
225
    }
226
  };
227

228
  const onReleaseInputChange = inputValue => {
241✔
229
    setIsLoadingReleases(!releases.length);
×
230
    return getReleases({ page: 1, perPage: 100, searchTerm: inputValue, searchOnly: true }).finally(() => setIsLoadingReleases(false));
×
231
  };
232

233
  const releaseDeviceTypes = (deploymentRelease && deploymentRelease.device_types_compatible) ?? [];
241✔
234
  const devicetypesInfo = (
235
    <Tooltip title={<p>{releaseDeviceTypes.join(', ')}</p>} placement="bottom">
241✔
236
      <span className="link">
237
        {releaseDeviceTypes.length} device {pluralize('types', releaseDeviceTypes.length)}
238
      </span>
239
    </Tooltip>
240
  );
241

242
  return (
241✔
243
    <>
244
      <h4 className="margin-bottom-none margin-top-none">Select a Release to deploy</h4>
245
      <div className={commonClasses.columns}>
246
        <div ref={releaseRef} className={classes.selection}>
247
          {releaseSelectionLocked ? (
241✔
248
            <TextField value={deploymentRelease?.Name} label="Release" disabled={true} className={classes.infoStyle} />
249
          ) : (
250
            <AsyncAutocomplete
251
              id="deployment-release-selection"
252
              initialValue={deploymentRelease?.Name}
253
              labelAttribute="Name"
254
              placeholder="Select a Release"
255
              selectionAttribute="Name"
256
              options={releaseItems}
257
              onChange={onReleaseInputChange}
258
              onChangeSelection={onReleaseSelectionChange}
259
              isLoading={isLoadingReleases}
260
              styles={hardCodedStyle}
261
            />
262
          )}
263
          {!releaseItems.length ? (
241✔
264
            <ReleasesWarning lacksReleases />
265
          ) : (
266
            !!releaseDeviceTypes.length && <InfoText style={{ marginBottom: 0 }}>This Release is compatible with {devicetypesInfo}.</InfoText>
239✔
267
          )}
268
        </div>
269
        <InfoHint content="The deployment will skip any devices in the group that are already on the target Release version, or that have an incompatible device type." />
270
      </div>
271
    </>
272
  );
273
};
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