• 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

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

17
import { CloudUpload } from '@mui/icons-material';
18
import { Button, Tab, Tabs, TextField } from '@mui/material';
19
import { makeStyles } from 'tss-react/mui';
20

21
import pluralize from 'pluralize';
22

23
import { getExistingReleaseTags, getReleases, getUpdateTypes, selectRelease, setReleasesListState } from '../../actions/releaseActions';
24
import { BENEFITS, SORTING_OPTIONS, TIMEOUTS } from '../../constants/appConstants';
25
import {
26
  getHasReleases,
27
  getIsEnterprise,
28
  getReleaseListState,
29
  getReleaseTags,
30
  getReleasesList,
31
  getSelectedRelease,
32
  getUpdateTypes as getUpdateTypesSelector,
33
  getUserCapabilities
34
} from '../../selectors';
35
import { useDebounce } from '../../utils/debouncehook';
36
import { useLocationParams } from '../../utils/liststatehook';
37
import ChipSelect from '../common/chipselect';
38
import EnterpriseNotification, { DefaultUpgradeNotification } from '../common/enterpriseNotification';
39
import { ControlledAutoComplete } from '../common/forms/autocomplete';
40
import { Filters } from '../common/forms/filters';
41
import { ControlledSearch } from '../common/search';
42
import { HELPTOOLTIPS, MenderHelpTooltip } from '../helptips/helptooltips';
43
import AddArtifactDialog from './dialogs/addartifact';
44
import ReleaseDetails from './releasedetails';
45
import ReleasesList from './releaseslist';
46

47
const refreshArtifactsLength = 60000;
4✔
48

49
const DeltaProgress = ({ className = '' }) => {
4!
50
  const isEnterprise = useSelector(getIsEnterprise);
×
51
  return (
×
52
    <div className={`dashboard-placeholder ${className}`} style={{ display: 'grid', placeContent: 'center' }}>
53
      {isEnterprise ? 'There is no automatic delta artifacts generation running.' : <DefaultUpgradeNotification />}
×
54
    </div>
55
  );
56
};
57

58
const DeltaTitle = () => (
4✔
59
  <div className="flexbox center-aligned">
42✔
60
    <div>Delta Artifacts generation</div>
61
    <EnterpriseNotification className="margin-left-small" id={BENEFITS.deltaGeneration.id} />
62
  </div>
63
);
64

65
const tabs = [
4✔
66
  { key: 'releases', Title: () => 'Releases', component: ReleasesList },
42✔
67
  { key: 'delta', Title: DeltaTitle, component: DeltaProgress }
68
];
69

70
const useStyles = makeStyles()(theme => ({
8✔
71
  container: { maxWidth: 1600 },
72
  searchNote: { minHeight: '1.8rem' },
73
  tabContainer: { alignSelf: 'flex-start' },
74
  uploadButton: { minWidth: 164, marginRight: theme.spacing(2) }
75
}));
76

77
const Header = ({ canUpload, releasesListState, setReleasesListState, onUploadClick }) => {
4✔
78
  const { selectedTags = [], searchTerm = '', searchTotal, tab = tabs[0].key, total, type } = releasesListState;
42!
79
  const { classes } = useStyles();
42✔
80
  const hasReleases = useSelector(getHasReleases);
42✔
81
  const existingTags = useSelector(getReleaseTags);
42✔
82
  const updateTypes = useSelector(getUpdateTypesSelector);
42✔
83

84
  const searchUpdated = useCallback(searchTerm => setReleasesListState({ searchTerm }), [setReleasesListState]);
42✔
85

86
  const onTabChanged = (e, tab) => setReleasesListState({ tab });
42✔
87

88
  const onFiltersChange = useCallback(({ name, tags, type }) => setReleasesListState({ selectedTags: tags, searchTerm: name, type }), [setReleasesListState]);
42✔
89

90
  return (
42✔
91
    <div>
92
      <div className="flexbox space-between center-aligned">
93
        <Tabs className={classes.tabContainer} value={tab} onChange={onTabChanged} textColor="primary">
94
          {tabs.map(({ key, Title }) => (
95
            <Tab key={key} label={<Title />} value={key} />
84✔
96
          ))}
97
        </Tabs>
98
        {canUpload && (
84✔
99
          <div className="flexbox center-aligned">
100
            <Button color="secondary" className={classes.uploadButton} onClick={onUploadClick} startIcon={<CloudUpload fontSize="small" />} variant="contained">
101
              Upload
102
            </Button>
103
            <MenderHelpTooltip id={HELPTOOLTIPS.artifactUpload.id} style={{ marginTop: 8 }} />
104
          </div>
105
        )}
106
      </div>
107
      {hasReleases && tab === tabs[0].key && (
126✔
108
        <Filters
109
          className={classes.container}
110
          onChange={onFiltersChange}
111
          initialValues={{ name: searchTerm, tags: selectedTags, type }}
112
          defaultValues={{ name: '', tags: [], type: '' }}
113
          filters={[
114
            {
115
              key: 'name',
116
              title: 'Release name',
117
              Component: ControlledSearch,
118
              componentProps: {
119
                onSearch: searchUpdated,
120
                placeholder: 'Starts with'
121
              }
122
            },
123
            {
124
              key: 'tags',
125
              title: 'Tags',
126
              Component: ChipSelect,
127
              componentProps: {
128
                options: existingTags,
129
                placeholder: 'Select tags',
130
                selection: selectedTags
131
              }
132
            },
133
            {
134
              key: 'type',
135
              title: 'Contains Artifact type',
136
              Component: ControlledAutoComplete,
137
              componentProps: {
138
                autoHighlight: true,
139
                autoSelect: true,
140
                filterSelectedOptions: true,
141
                freeSolo: true,
142
                handleHomeEndKeys: true,
143
                options: updateTypes,
144
                renderInput: params => <TextField {...params} placeholder="Any" InputProps={{ ...params.InputProps }} />
48✔
145
              }
146
            }
147
          ]}
148
        />
149
      )}
150
      <p className={`muted ${classes.searchNote}`}>{searchTerm && searchTotal !== total ? `Filtered from ${total} ${pluralize('Release', total)}` : ''}</p>
85✔
151
    </div>
152
  );
153
};
154

155
export const Releases = () => {
4✔
156
  const releasesListState = useSelector(getReleaseListState);
42✔
157
  const { searchTerm, sort = {}, page, perPage, tab = tabs[0].key, selectedTags, type } = releasesListState;
42!
158
  const releases = useSelector(getReleasesList);
42✔
159
  const selectedRelease = useSelector(getSelectedRelease);
42✔
160
  const { canUploadReleases } = useSelector(getUserCapabilities);
42✔
161
  const dispatch = useDispatch();
42✔
162
  const { classes } = useStyles();
42✔
163

164
  const [selectedFile, setSelectedFile] = useState();
42✔
165
  const [showAddArtifactDialog, setShowAddArtifactDialog] = useState(false);
42✔
166
  const artifactTimer = useRef();
42✔
167
  const [locationParams, setLocationParams] = useLocationParams('releases', { defaults: { direction: SORTING_OPTIONS.desc, key: 'modified' } });
42✔
168
  const debouncedSearchTerm = useDebounce(searchTerm, TIMEOUTS.debounceDefault);
42✔
169
  const debouncedTypeFilter = useDebounce(type, TIMEOUTS.debounceDefault);
42✔
170

171
  useEffect(() => {
42✔
172
    if (!artifactTimer.current) {
17✔
173
      return;
4✔
174
    }
175
    setLocationParams({ pageState: { ...releasesListState, selectedRelease: selectedRelease.name } });
13✔
176
    // eslint-disable-next-line react-hooks/exhaustive-deps
177
  }, [
178
    debouncedSearchTerm,
179
    debouncedTypeFilter,
180
    // eslint-disable-next-line react-hooks/exhaustive-deps
181
    JSON.stringify(sort),
182
    page,
183
    perPage,
184
    selectedRelease.name,
185
    setLocationParams,
186
    tab,
187
    // eslint-disable-next-line react-hooks/exhaustive-deps
188
    JSON.stringify(selectedTags)
189
  ]);
190

191
  useEffect(() => {
42✔
192
    const { selectedRelease, tags, ...remainder } = locationParams;
9✔
193
    if (selectedRelease) {
9✔
194
      dispatch(selectRelease(selectedRelease));
2✔
195
    }
196
    dispatch(setReleasesListState({ ...remainder, selectedTags: tags }));
9✔
197
    clearInterval(artifactTimer.current);
9✔
198
    artifactTimer.current = setInterval(() => dispatch(getReleases()), refreshArtifactsLength);
9✔
199
    return () => {
9✔
200
      clearInterval(artifactTimer.current);
9✔
201
    };
202
    // eslint-disable-next-line react-hooks/exhaustive-deps
203
  }, [dispatch, JSON.stringify(locationParams)]);
204

205
  useEffect(() => {
42✔
206
    dispatch(getReleases({ searchTerm: '', searchOnly: true, page: 1, perPage: 1, selectedTags: [], type: '' }));
4✔
207
    dispatch(getExistingReleaseTags());
4✔
208
    dispatch(getUpdateTypes());
4✔
209
  }, [dispatch]);
210

211
  const onUploadClick = () => setShowAddArtifactDialog(true);
42✔
212

213
  const onFileUploadClick = selectedFile => {
42✔
214
    setSelectedFile(selectedFile);
×
215
    setShowAddArtifactDialog(true);
×
216
  };
217

218
  const onHideAddArtifactDialog = () => setShowAddArtifactDialog(false);
42✔
219

220
  const onSetReleasesListState = useCallback(state => dispatch(setReleasesListState(state)), [dispatch]);
42✔
221

222
  const ContentComponent = useMemo(() => tabs.find(({ key }) => key === tab).component, [tab]);
42✔
223
  return (
42✔
224
    <div className="margin">
225
      <div>
226
        <Header
227
          canUpload={canUploadReleases}
228
          onUploadClick={onUploadClick}
229
          releasesListState={releasesListState}
230
          setReleasesListState={onSetReleasesListState}
231
        />
232
        <ContentComponent className={classes.container} onFileUploadClick={onFileUploadClick} />
233
      </div>
234
      <ReleaseDetails />
235
      {showAddArtifactDialog && (
42!
236
        <AddArtifactDialog releases={releases} onCancel={onHideAddArtifactDialog} onUploadStarted={onHideAddArtifactDialog} selectedFile={selectedFile} />
237
      )}
238
    </div>
239
  );
240
};
241

242
export default Releases;
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