• 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

72.0
/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;
2,345✔
66
  const deviceList = (isUUID(name) && devicesById[name] ? [devicesById[name]] : Array.isArray(devices) ? devices : Object.values(devices)) ?? [];
2,345!
67
  if (type !== DEPLOYMENT_TYPES.configuration && (!deviceList.length || group || (deployment.name !== undefined && !isUUID(name)))) {
2,345!
68
    return (group || name) ?? '';
2,345!
69
  }
70
  return deviceList.map(device => getDeviceIdentityText({ device, idAttribute }) ?? device?.id).join(', ');
×
71
};
72

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

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

99
  const { deploymentDeviceCount = 0, devices = [], group = null } = deploymentObject;
247✔
100
  const device = devices.length === 1 ? devices[0] : {};
247!
101

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

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

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

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

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

207
  useEffect(() => {
247✔
208
    setIsLoadingReleases(!releases.length);
9✔
209
  }, [releases.length]);
210

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

220
  const onReleaseSelectionChange = release => {
247✔
221
    if (release !== deploymentObject.release) {
2!
222
      setDeploymentSettings({ release });
2✔
223
    }
224
  };
225

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

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

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