• 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

63.04
/src/js/components/releases/releasedetails.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, { useMemo, useRef, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16
import { useNavigate } from 'react-router-dom';
17

18
// material ui
19
import {
20
  Close as CloseIcon,
21
  Edit as EditIcon,
22
  HighlightOffOutlined as HighlightOffOutlinedIcon,
23
  Link as LinkIcon,
24
  Replay as ReplayIcon,
25
  Sort as SortIcon
26
} from '@mui/icons-material';
27
import { Button, Collapse, Divider, Drawer, IconButton, SpeedDial, SpeedDialAction, SpeedDialIcon, Tooltip } from '@mui/material';
28
import { speedDialActionClasses } from '@mui/material/SpeedDialAction';
29
import { makeStyles } from 'tss-react/mui';
30

31
import copy from 'copy-to-clipboard';
32

33
import { setSnackbar } from '../../actions/appActions';
34
import { advanceOnboarding } from '../../actions/onboardingActions';
35
import { removeArtifact, removeRelease, selectArtifact, selectRelease } from '../../actions/releaseActions';
36
import { DEPLOYMENT_ROUTES } from '../../constants/deploymentConstants';
37
import { onboardingSteps } from '../../constants/onboardingConstants';
38
import { FileSize, customSort, formatTime, toggle } from '../../helpers';
39
import { getFeatures, getOnboardingState, getShowHelptips, getUserCapabilities } from '../../selectors';
40
import { getOnboardingComponentFor } from '../../utils/onboardingmanager';
41
import useWindowSize from '../../utils/resizehook';
42
import ChipSelect from '../common/chipselect';
43
import { RelativeTime } from '../common/time';
44
import { ExpandArtifact } from '../helptips/helptooltips';
45
import Artifact from './artifact';
46
import RemoveArtifactDialog from './dialogs/removeartifact';
47

48
const DeviceTypeCompatibility = ({ artifact }) => {
6✔
49
  const compatible = artifact.artifact_depends ? artifact.artifact_depends.device_type.join(', ') : artifact.device_types_compatible.join(', ');
23✔
50
  return (
23✔
51
    <Tooltip title={compatible} placement="top-start">
52
      <div className="text-overflow">{compatible}</div>
53
    </Tooltip>
54
  );
55
};
56

57
export const columns = [
6✔
58
  {
59
    title: 'Device type compatibility',
60
    name: 'device_types',
61
    sortable: false,
62
    render: DeviceTypeCompatibility
63
  },
64
  {
65
    title: 'Type',
66
    name: 'type',
67
    sortable: false,
68
    render: ({ artifact }) => <div style={{ maxWidth: '100vw' }}>{artifact.updates.reduce((accu, item) => (accu ? accu : item.type_info.type), '')}</div>
23!
69
  },
70
  { title: 'Size', name: 'size', sortable: true, render: ({ artifact }) => <FileSize fileSize={artifact.size} /> },
23✔
71
  { title: 'Last modified', name: 'modified', sortable: true, render: ({ artifact }) => <RelativeTime updateTime={formatTime(artifact.modified)} /> }
23✔
72
];
73

74
const defaultActions = [
6✔
75
  {
76
    action: ({ onCreateDeployment, selection }) => onCreateDeployment(selection),
×
77
    icon: <ReplayIcon />,
78
    isApplicable: ({ userCapabilities: { canDeploy } }) => canDeploy,
2✔
79
    key: 'deploy',
80
    title: 'Create a deployment for this release'
81
  },
82
  {
83
    action: ({ onDeleteRelease, selection }) => onDeleteRelease(selection),
×
84
    icon: <HighlightOffOutlinedIcon className="red" />,
85
    isApplicable: ({ userCapabilities: { canManageReleases } }) => canManageReleases,
2✔
86
    key: 'delete',
87
    title: 'Delete release'
88
  }
89
];
90

91
const useStyles = makeStyles()(theme => ({
6✔
92
  container: {
93
    display: 'flex',
94
    position: 'fixed',
95
    bottom: theme.spacing(6.5),
96
    right: theme.spacing(6.5),
97
    zIndex: 10,
98
    minWidth: 'max-content',
99
    alignItems: 'flex-end',
100
    justifyContent: 'flex-end',
101
    pointerEvents: 'none',
102
    [`.${speedDialActionClasses.staticTooltipLabel}`]: {
103
      minWidth: 'max-content'
104
    }
105
  },
106
  fab: { margin: theme.spacing(2) },
107
  tagSelect: { maxWidth: 350 },
108
  label: {
109
    marginRight: theme.spacing(2),
110
    marginBottom: theme.spacing(4)
111
  }
112
}));
113

114
export const ReleaseQuickActions = ({ actionCallbacks, innerRef, selectedRelease, userCapabilities }) => {
6✔
115
  const [showActions, setShowActions] = useState(false);
24✔
116
  const { classes } = useStyles();
24✔
117

118
  const actions = useMemo(() => {
24✔
119
    return Object.values(defaultActions).reduce((accu, action) => {
2✔
120
      if (action.isApplicable({ userCapabilities })) {
4!
121
        accu.push(action);
4✔
122
      }
123
      return accu;
4✔
124
    }, []);
125
  }, [JSON.stringify(userCapabilities)]);
126

127
  return (
24✔
128
    <div className={classes.container} ref={innerRef}>
129
      <div className={classes.label}>Release actions</div>
130
      <SpeedDial
131
        className={classes.fab}
132
        ariaLabel="device-actions"
133
        icon={<SpeedDialIcon />}
134
        onClose={() => setShowActions(false)}
×
135
        onOpen={setShowActions}
136
        open={Boolean(showActions)}
137
      >
138
        {actions.map(action => (
139
          <SpeedDialAction
48✔
140
            key={action.key}
141
            aria-label={action.key}
142
            icon={action.icon}
143
            tooltipTitle={action.title}
144
            tooltipOpen
145
            onClick={() => action.action({ ...actionCallbacks, selection: selectedRelease })}
×
146
          />
147
        ))}
148
      </SpeedDial>
149
    </div>
150
  );
151
};
152

153
const OnboardingComponent = ({ creationRef, drawerRef, onboardingState }) => {
6✔
154
  if (!(creationRef.current && drawerRef.current)) {
24✔
155
    return null;
2✔
156
  }
157
  const anchor = {
22✔
158
    anchor: {
159
      left: creationRef.current.offsetLeft - drawerRef.current.offsetLeft - 48,
160
      top: creationRef.current.offsetTop + creationRef.current.offsetHeight - 48
161
    },
162
    place: 'left'
163
  };
164
  let onboardingComponent = getOnboardingComponentFor(onboardingSteps.ARTIFACT_INCLUDED_DEPLOY_ONBOARDING, onboardingState, anchor);
22✔
165
  return getOnboardingComponentFor(onboardingSteps.ARTIFACT_MODIFIED_ONBOARDING, onboardingState, anchor, onboardingComponent);
22✔
166
};
167

168
const ReleaseTags = ({ existingTags = [] }) => {
6!
169
  const [selectedTags, setSelectedTags] = useState(existingTags);
×
170
  const [isEditing, setIsEditing] = useState(false);
×
171

172
  const onToggleEdit = () => {
×
173
    setSelectedTags(existingTags);
×
174
    setIsEditing(toggle);
×
175
  };
176

177
  const onTagSelectionChanged = ({ selection }) => setSelectedTags(selection);
×
178

179
  const onSave = () => {
×
180
    console.log('saving tags', selectedTags);
×
181
  };
182

183
  const { classes } = useStyles();
×
184

185
  return (
×
186
    <div className="margin-bottom" style={{ maxWidth: 500 }}>
187
      <div className="flexbox center-aligned">
188
        <h4 className="margin-right">Tags</h4>
189
        {!isEditing && (
×
190
          <Button onClick={onToggleEdit} size="small" startIcon={<EditIcon />}>
191
            Edit
192
          </Button>
193
        )}
194
      </div>
195
      <ChipSelect
196
        className={classes.tagSelect}
197
        id="release-tags"
198
        label=""
199
        onChange={onTagSelectionChanged}
200
        disabled={!isEditing}
201
        key={`${isEditing}`}
202
        placeholder={isEditing ? 'Enter release tags' : 'Click edit to add release tags'}
×
203
        selection={selectedTags}
204
        options={existingTags}
205
      />
206
      <Collapse in={isEditing}>
207
        <div className="flexbox center-aligned margin-top-small" style={{ justifyContent: 'end' }}>
208
          <Button variant="contained" onClick={onSave} color="secondary" style={{ marginRight: 10 }}>
209
            Save
210
          </Button>
211
          <Button onClick={onToggleEdit}>Cancel</Button>
212
        </div>
213
      </Collapse>
214
    </div>
215
  );
216
};
217

218
const ArtifactsList = ({ artifacts, selectArtifact, selectedArtifact, setShowRemoveArtifactDialog, showHelptips }) => {
6✔
219
  const [sortCol, setSortCol] = useState('modified');
24✔
220
  const [sortDown, setSortDown] = useState(true);
24✔
221

222
  const onRowSelection = artifact => {
24✔
223
    if (!artifact || !selectedArtifact || selectedArtifact.id !== artifact.id) {
×
224
      return selectArtifact(artifact);
×
225
    }
226
    selectArtifact();
×
227
  };
228

229
  const sortColumn = col => {
24✔
230
    if (!col.sortable) {
×
231
      return;
×
232
    }
233
    // sort table
234
    setSortDown(toggle);
×
235
    setSortCol(col);
×
236
  };
237

238
  if (!artifacts.length) {
24✔
239
    return null;
2✔
240
  }
241

242
  const items = artifacts.sort(customSort(sortDown, sortCol));
22✔
243

244
  return (
22✔
245
    <>
246
      <h4>Artifacts in this Release:</h4>
247
      <div>
248
        <div className="release-repo-item repo-item repo-header">
249
          {columns.map(item => (
250
            <Tooltip key={item.name} className="columnHeader" title={item.title} placement="top-start" onClick={() => sortColumn(item)}>
88✔
251
              <div>
252
                {item.title}
253
                {item.sortable ? <SortIcon className={`sortIcon ${sortCol === item.name ? 'selected' : ''} ${sortDown.toString()}`} /> : null}
132✔
254
              </div>
255
            </Tooltip>
256
          ))}
257
          <div style={{ width: 48 }} />
258
        </div>
259
        {items.map((pkg, index) => {
260
          const expanded = !!(selectedArtifact && selectedArtifact.id === pkg.id);
22✔
261
          return (
22✔
262
            <Artifact
263
              key={`repository-item-${index}`}
264
              artifact={pkg}
265
              columns={columns}
266
              expanded={expanded}
267
              index={index}
268
              onRowSelection={() => onRowSelection(pkg)}
×
269
              // this will be run after expansion + collapse and both need some time to fully settle
270
              // otherwise the measurements are off
271
              showRemoveArtifactDialog={setShowRemoveArtifactDialog}
272
            />
273
          );
274
        })}
275
      </div>
276
      {showHelptips && (
44✔
277
        <span className="relative">
278
          <ExpandArtifact />
279
        </span>
280
      )}
281
    </>
282
  );
283
};
284

285
export const ReleaseDetails = () => {
6✔
286
  const [showRemoveDialog, setShowRemoveArtifactDialog] = useState(false);
37✔
287
  const [confirmReleaseDeletion, setConfirmReleaseDeletion] = useState(false);
37✔
288
  // eslint-disable-next-line no-unused-vars
289
  const windowSize = useWindowSize();
37✔
290
  const creationRef = useRef();
37✔
291
  const drawerRef = useRef();
37✔
292
  const navigate = useNavigate();
37✔
293
  const dispatch = useDispatch();
37✔
294
  const { hasReleaseTags } = useSelector(getFeatures);
37✔
295
  const onboardingState = useSelector(getOnboardingState);
37✔
296
  const pastDeploymentsCount = useSelector(state => state.deployments.byStatus.finished.total);
68✔
297
  const release = useSelector(state => state.releases.byId[state.releases.selectedRelease]) ?? {};
68✔
298
  const selectedArtifact = useSelector(state => state.releases.selectedArtifact);
68✔
299
  const showHelptips = useSelector(getShowHelptips);
37✔
300
  const userCapabilities = useSelector(getUserCapabilities);
37✔
301

302
  const onRemoveArtifact = artifact => dispatch(removeArtifact(artifact.id)).finally(() => setShowRemoveArtifactDialog(false));
37✔
303

304
  const copyLinkToClipboard = () => {
37✔
305
    const location = window.location.href.substring(0, window.location.href.indexOf('/releases') + '/releases'.length);
×
306
    copy(`${location}/${release.Name}`);
×
307
    dispatch(setSnackbar('Link copied to clipboard'));
×
308
  };
309

310
  const onCloseClick = () => dispatch(selectRelease());
37✔
311

312
  const onCreateDeployment = () => {
37✔
313
    if (!onboardingState.complete) {
×
314
      dispatch(advanceOnboarding(onboardingSteps.ARTIFACT_INCLUDED_DEPLOY_ONBOARDING));
×
315
      if (pastDeploymentsCount === 1) {
×
316
        dispatch(advanceOnboarding(onboardingSteps.ARTIFACT_MODIFIED_ONBOARDING));
×
317
      }
318
    }
319
    navigate(`${DEPLOYMENT_ROUTES.active.route}?open=true&release=${encodeURIComponent(release.Name)}`);
×
320
  };
321

322
  const onToggleReleaseDeletion = () => setConfirmReleaseDeletion(toggle);
37✔
323

324
  const onDeleteRelease = () => dispatch(removeRelease(release.Name)).then(() => setConfirmReleaseDeletion(false));
37✔
325

326
  const artifacts = release.Artifacts ?? [];
37✔
327
  return (
37✔
328
    <Drawer anchor="right" open={!!release.Name} onClose={onCloseClick} PaperProps={{ style: { minWidth: '60vw' }, ref: drawerRef }}>
329
      <div className="flexbox center-aligned space-between">
330
        <div className="flexbox center-aligned">
331
          <b>
332
            Release information for <i>{release.Name}</i>
333
          </b>
334
          <IconButton onClick={copyLinkToClipboard} size="large">
335
            <LinkIcon />
336
          </IconButton>
337
        </div>
338
        <div className="flexbox center-aligned">
339
          <div className="muted margin-right flexbox">
340
            <div className="margin-right-small">Last modified:</div>
341
            <RelativeTime updateTime={release.modified} />
342
          </div>
343
          <IconButton onClick={onCloseClick} aria-label="close" size="large">
344
            <CloseIcon />
345
          </IconButton>
346
        </div>
347
      </div>
348
      <Divider className="margin-bottom" />
349
      {hasReleaseTags && <ReleaseTags />}
37!
350
      <ArtifactsList
351
        artifacts={artifacts}
352
        selectArtifact={artifact => dispatch(selectArtifact(artifact))}
×
353
        selectedArtifact={selectedArtifact}
354
        setShowRemoveArtifactDialog={setShowRemoveArtifactDialog}
355
        showHelptips={showHelptips}
356
      />
357
      <OnboardingComponent creationRef={creationRef} drawerRef={drawerRef} onboardingState={onboardingState} />
358
      <RemoveArtifactDialog
359
        artifact={selectedArtifact}
360
        open={!!showRemoveDialog}
361
        onCancel={() => setShowRemoveArtifactDialog(false)}
2✔
362
        onRemove={() => onRemoveArtifact(selectedArtifact)}
×
363
      />
364
      <RemoveArtifactDialog open={!!confirmReleaseDeletion} onRemove={onDeleteRelease} onCancel={onToggleReleaseDeletion} release={release} />
365
      <ReleaseQuickActions
366
        actionCallbacks={{ onCreateDeployment, onDeleteRelease: onToggleReleaseDeletion }}
367
        innerRef={creationRef}
368
        selectedRelease={release}
369
        userCapabilities={userCapabilities}
370
      />
371
    </Drawer>
372
  );
373
};
374

375
export default ReleaseDetails;
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