• 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

82.81
/src/js/components/releases/artifactdetails.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, { useCallback, useEffect, useMemo, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16

17
// material ui
18
import {
19
  Add as AddIcon,
20
  Cancel as CancelIcon,
21
  CancelOutlined as CancelOutlinedIcon,
22
  CheckCircleOutline as CheckCircleOutlineIcon,
23
  ExitToApp as ExitToAppIcon,
24
  Launch as LaunchIcon,
25
  Remove as RemoveIcon
26
} from '@mui/icons-material';
27
import { Accordion, AccordionDetails, AccordionSummary, Button, List, ListItem, ListItemText } from '@mui/material';
28
import { makeStyles } from 'tss-react/mui';
29

30
import pluralize from 'pluralize';
31

32
import { editArtifact, getArtifactInstallCount, getArtifactUrl } from '../../actions/releaseActions';
33
import { extractSoftware, extractSoftwareItem, toggle } from '../../helpers';
34
import { getUserCapabilities } from '../../selectors';
35
import ExpandableAttribute from '../common/expandable-attribute';
36
import ArtifactPayload from './artifactPayload';
37
import ArtifactMetadataList from './artifactmetadatalist';
38
import { EditableLongText } from './releasedetails';
39

40
const useStyles = makeStyles()(theme => ({
7✔
41
  link: { marginTop: theme.spacing() },
42
  listItemStyle: {
43
    bordered: {
44
      borderBottom: '1px solid',
45
      borderBottomColor: theme.palette.grey[500]
46
    },
47
    color: theme.palette.text.primary,
48
    fontSize: 13,
49
    marginRight: '2vw',
50
    minWidth: 200,
51
    padding: 0,
52
    width: 'initial'
53
  },
54
  paddingOverride: { paddingBottom: 4, paddingTop: 0 },
55
  accordPanel1: {
56
    background: theme.palette.grey[500],
57
    borderTop: 'none',
58
    padding: '0 15px',
59
    marginBottom: 30,
60
    [`&.Mui-expanded`]: {
61
      background: theme.palette.grey[500],
62
      marginBottom: 30
63
    }
64
  },
65
  accordSummary: {
66
    background: theme.palette.grey[500],
67
    padding: 0
68
  }
69
}));
70

71
export const transformArtifactCapabilities = (capabilities = {}) => {
7✔
72
  return Object.entries(capabilities).reduce((accu, [key, value]) => {
87✔
73
    if (!Array.isArray(value)) {
109✔
74
      accu.push({ key, primary: key, secondary: value });
55✔
75
    } else if (!key.startsWith('device_type')) {
54✔
76
      // we can expect this to be an array of artifacts or artifact groups this artifact depends on
77
      const dependencies = value.reduce((dependencies, dependency, index) => {
27✔
78
        const dependencyKey = value.length > 1 ? `${key}-${index + 1}` : key;
54!
79
        dependencies.push({ key: dependencyKey, primary: dependencyKey, secondary: dependency });
54✔
80
        return dependencies;
54✔
81
      }, []);
82
      accu.push(...dependencies);
27✔
83
    }
84
    return accu;
109✔
85
  }, []);
86
};
87

88
export const transformArtifactMetadata = (metadata = {}) => {
7✔
89
  return Object.entries(metadata).reduce((accu, [key, value]) => {
29✔
90
    const commonProps = { key, primary: key, secondaryTypographyProps: { component: 'div' } };
4✔
91
    if (Array.isArray(value)) {
4✔
92
      accu.push({ ...commonProps, secondary: value.length ? value.join(',') : '-' });
1!
93
    } else if (value instanceof Object) {
3✔
94
      accu.push({ ...commonProps, secondary: JSON.stringify(value) || '-' });
1!
95
    } else {
96
      accu.push({ ...commonProps, secondary: value || '-' });
2✔
97
    }
98
    return accu;
4✔
99
  }, []);
100
};
101

102
const DevicesLink = ({ artifact: { installCount }, softwareItem: { key, name, version } }) => {
7✔
103
  const { classes } = useStyles();
×
104
  const text = `${installCount} ${pluralize('device', installCount)}`;
×
105
  if (!installCount) {
×
106
    return <div className={classes.link}>{text}</div>;
×
107
  }
108
  const attribute = `${key}${name ? `.${name}` : ''}.version`;
×
109
  return (
×
110
    <a
111
      className={`flexbox center-aligned ${classes.link}`}
112
      href={`${window.location.origin}/ui/devices/accepted?inventory=${attribute}:eq:${version}`}
113
      target="_blank"
114
      rel="noreferrer"
115
    >
116
      {text}
117
      <LaunchIcon className="margin-left-small" fontSize="small" />
118
    </a>
119
  );
120
};
121

122
export const ArtifactDetails = ({ artifact, open, showRemoveArtifactDialog }) => {
7✔
123
  const { classes } = useStyles();
28✔
124
  const [showPayloads, setShowPayloads] = useState(false);
28✔
125
  const [showProvidesDepends, setShowProvidesDepends] = useState(false);
28✔
126

127
  const dispatch = useDispatch();
28✔
128

129
  const { canManageReleases } = useSelector(getUserCapabilities);
28✔
130

131
  const softwareVersions = useMemo(() => {
28✔
132
    const { software } = extractSoftware(artifact.artifact_provides);
4✔
133
    return software.reduce((accu, item) => {
4✔
134
      const infoItems = item[0].split('.');
2✔
135
      if (infoItems[infoItems.length - 1] !== 'version') {
2!
136
        return accu;
×
137
      }
138
      accu.push({ key: infoItems[0], name: infoItems.slice(1, infoItems.length - 1).join('.'), version: item[1], nestingLevel: infoItems.length });
2✔
139
      return accu;
2✔
140
    }, []);
141
    // eslint-disable-next-line react-hooks/exhaustive-deps
142
  }, [JSON.stringify(artifact.artifact_provides)]);
143

144
  useEffect(() => {
28✔
145
    if (artifact.url || !open) {
6✔
146
      return;
4✔
147
    }
148
    dispatch(getArtifactUrl(artifact.id));
2✔
149
  }, [artifact.id, artifact.url, dispatch, open]);
150

151
  useEffect(() => {
28✔
152
    if (artifact.installCount || !open || softwareVersions.length > 1) {
6✔
153
      return;
4✔
154
    }
155
    const { version } = softwareVersions.sort((a, b) => a.nestingLevel - b.nestingLevel).reduce((accu, item) => accu ?? item, undefined) ?? {};
2!
156
    if (version) {
2!
157
      dispatch(getArtifactInstallCount(artifact.id));
2✔
158
    }
159
    // eslint-disable-next-line react-hooks/exhaustive-deps
160
  }, [artifact.id, artifact.installCount, dispatch, open, softwareVersions.length]);
161

162
  const onDescriptionChanged = useCallback(description => dispatch(editArtifact(artifact.id, { description })), [artifact.id, dispatch]);
28✔
163

164
  const softwareItem = extractSoftwareItem(artifact.artifact_provides);
28✔
165
  const softwareInformation = softwareItem
28✔
166
    ? {
167
        title: 'Software versioning information',
168
        content: [
169
          { primary: 'Software filesystem', secondary: softwareItem.key },
170
          { primary: 'Software name', secondary: softwareItem.name },
171
          { primary: 'Software version', secondary: softwareItem.version }
172
        ]
173
      }
174
    : { content: [] };
175

176
  const artifactMetaInfo = [
28✔
177
    { title: 'Depends', content: transformArtifactCapabilities(artifact.artifact_depends) },
178
    { title: 'Clears', content: transformArtifactCapabilities(artifact.artifact_clears) },
179
    { title: 'Provides', content: transformArtifactCapabilities(artifact.artifact_provides) },
180
    { title: 'Artifact metadata', content: transformArtifactMetadata(artifact.metaData) }
181
  ];
182
  const hasMetaInfo = artifactMetaInfo.some(item => !!item.content.length);
86✔
183
  const { installCount } = artifact;
28✔
184
  const itemProps = { classes: { root: 'attributes', disabled: 'opaque' }, className: classes.listItemStyle };
28✔
185
  return (
28✔
186
    <div className={artifact.name == null ? 'muted' : null}>
28✔
187
      <List className="list-horizontal-flex">
188
        <ListItem {...itemProps}>
189
          <ListItemText
190
            primary="Description"
191
            style={{ marginBottom: -3, minWidth: 600 }}
192
            primaryTypographyProps={{ style: { marginBottom: 3 } }}
193
            secondary={<EditableLongText fullWidth original={artifact.description} onChange={onDescriptionChanged} />}
194
            secondaryTypographyProps={{ component: 'div' }}
195
          />
196
        </ListItem>
197
        <ListItem {...itemProps} className={`${classes.listItemStyle} ${classes.listItemStyle.bordered}`}>
198
          <ListItemText primary="Signed" secondary={artifact.signed ? <CheckCircleOutlineIcon className="green" /> : <CancelOutlinedIcon className="red" />} />
28!
199
        </ListItem>
200
        {installCount !== undefined && softwareVersions.length === 1 && (
28!
201
          <ExpandableAttribute
202
            classes={{ root: classes.paddingOverride }}
203
            disableGutters
204
            primary="Installed on"
205
            secondary={<DevicesLink artifact={artifact} softwareItem={softwareItem} />}
206
            secondaryTypographyProps={{ title: `installed on ${installCount} ${pluralize('device', installCount)}` }}
207
            style={{ padding: 0 }}
208
          />
209
        )}
210
      </List>
211
      <ArtifactMetadataList metaInfo={softwareInformation} />
212
      <Accordion square expanded={showPayloads} onChange={() => setShowPayloads(toggle)} className={classes.accordPanel1}>
×
213
        <AccordionSummary className={classes.accordSummary}>
214
          <p>Artifact contents</p>
215
          <div style={{ marginLeft: 'auto' }}>{showPayloads ? <RemoveIcon /> : <AddIcon />}</div>
28!
216
        </AccordionSummary>
217
        <AccordionDetails className={classes.accordSummary}>
218
          {showPayloads &&
28!
219
            !!artifact.updates.length &&
220
            artifact.updates.map((update, index) => <ArtifactPayload index={index} payload={update} key={`artifact-update-${index}`} />)}
×
221
        </AccordionDetails>
222
      </Accordion>
223
      {hasMetaInfo && (
54✔
224
        <Accordion square expanded={showProvidesDepends} onChange={() => setShowProvidesDepends(!showProvidesDepends)} className={classes.accordPanel1}>
×
225
          <AccordionSummary className={classes.accordSummary}>
226
            <p>Provides and Depends</p>
227
            <div style={{ marginLeft: 'auto' }}>{showProvidesDepends ? <RemoveIcon /> : <AddIcon />}</div>
26!
228
          </AccordionSummary>
229
          <AccordionDetails className={classes.accordSummary}>
230
            {showProvidesDepends && artifactMetaInfo.map((info, index) => <ArtifactMetadataList metaInfo={info} key={`artifact-info-${index}`} />)}
×
231
          </AccordionDetails>
232
        </Accordion>
233
      )}
234
      <div className="two-columns margin-top-small" style={{ maxWidth: 'fit-content' }}>
235
        {canManageReleases && (
56✔
236
          <>
237
            <Button
238
              href={artifact.url}
239
              target="_blank"
240
              disabled={!artifact.url}
241
              download={artifact.name ? `${artifact.name}.mender` : true}
28✔
242
              startIcon={<ExitToAppIcon style={{ transform: 'rotateZ(90deg)' }} />}
243
            >
244
              Download Artifact
245
            </Button>
246
            <Button onClick={showRemoveArtifactDialog} startIcon={<CancelIcon className="red auth" />}>
247
              Remove this Artifact?
248
            </Button>
249
          </>
250
        )}
251
      </div>
252
    </div>
253
  );
254
};
255

256
export default ArtifactDetails;
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