• 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

73.91
/src/js/components/settings/global.js
1
// Copyright 2018 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, useRef, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16

17
import { Button, Checkbox, FormControl, FormControlLabel, FormHelperText, InputLabel, MenuItem, Select, Switch, TextField } from '@mui/material';
18
import { makeStyles } from 'tss-react/mui';
19

20
import { getDeviceAttributes } from '../../actions/deviceActions';
21
import { changeNotificationSetting } from '../../actions/monitorActions';
22
import { getGlobalSettings, saveGlobalSettings } from '../../actions/userActions';
23
import { TIMEOUTS } from '../../constants/appConstants';
24
import { offlineThresholds } from '../../constants/deviceConstants';
25
import { alertChannels } from '../../constants/monitorConstants';
26
import { settingsKeys } from '../../constants/userConstants';
27
import {
28
  getFeatures,
29
  getGlobalSettings as getGlobalSettingsSelector,
30
  getIdAttribute,
31
  getOfflineThresholdSettings,
32
  getTenantCapabilities,
33
  getUserCapabilities,
34
  getUserRoles
35
} from '../../selectors';
36
import { useDebounce } from '../../utils/debouncehook';
37
import DocsLink from '../common/docslink';
38
import { HELPTOOLTIPS, MenderHelpTooltip } from '../helptips/helptooltips';
39
import ArtifactGenerationSettings from './artifactgeneration';
40
import ReportingLimits from './reportinglimits';
41

42
const maxWidth = 750;
6✔
43

44
const useStyles = makeStyles()(theme => ({
6✔
45
  threshold: {
46
    display: 'grid',
47
    gridTemplateColumns: '100px 100px',
48
    columnGap: theme.spacing(2)
49
  },
50
  textInput: {
51
    marginTop: 0,
52
    minWidth: 'initial'
53
  }
54
}));
55

56
export const IdAttributeSelection = ({ attributes, dialog = false, onCloseClick, onSaveClick, selectedAttribute = '' }) => {
6!
57
  const [attributeSelection, setAttributeSelection] = useState('name');
8✔
58

59
  useEffect(() => {
8✔
60
    setAttributeSelection(selectedAttribute);
1✔
61
  }, [selectedAttribute]);
62

63
  const changed = selectedAttribute !== attributeSelection;
8✔
64

65
  const onChangeIdAttribute = ({ target: { value: attributeSelection } }) => {
8✔
66
    setAttributeSelection(attributeSelection);
×
67
    if (dialog) {
×
68
      return;
×
69
    }
70
    onSaveClick(undefined, { attribute: attributeSelection, scope: attributes.find(({ value }) => value === attributeSelection).scope });
×
71
  };
72

73
  const undoChanges = e => {
8✔
74
    setAttributeSelection(selectedAttribute);
×
75
    if (dialog) {
×
76
      onCloseClick(e);
×
77
    }
78
  };
79

80
  const saveSettings = e => onSaveClick(e, { attribute: attributeSelection, scope: attributes.find(({ value }) => value === attributeSelection).scope });
8✔
81

82
  return (
8✔
83
    <div className="flexbox space-between" style={{ alignItems: 'flex-start', maxWidth }}>
84
      <FormControl>
85
        <InputLabel shrink id="device-id">
86
          Device identity attribute
87
        </InputLabel>
88
        <Select value={attributeSelection} onChange={onChangeIdAttribute}>
89
          {attributes.map(item => (
90
            <MenuItem key={item.value} value={item.value}>
25✔
91
              {item.label}
92
            </MenuItem>
93
          ))}
94
        </Select>
95
        <FormHelperText className="info" component="div">
96
          <div className="margin-top-small margin-bottom-small">Choose a device identity attribute to use to identify your devices throughout the UI.</div>
97
          <div className="margin-top-small margin-bottom-small">
98
            <DocsLink path="client-installation/identity" title="Learn how to add custom identity attributes" /> to your devices.
99
          </div>
100
        </FormHelperText>
101
      </FormControl>
102
      {dialog && (
8!
103
        <div className="margin-left margin-top flexbox">
104
          <Button onClick={undoChanges} style={{ marginRight: 10 }}>
105
            Cancel
106
          </Button>
107
          <Button variant="contained" onClick={saveSettings} disabled={!changed} color="primary">
108
            Save
109
          </Button>
110
        </div>
111
      )}
112
    </div>
113
  );
114
};
115

116
export const GlobalSettingsDialog = ({
6✔
117
  attributes,
118
  hasReporting,
119
  isAdmin,
120
  notificationChannelSettings,
121
  offlineThresholdSettings,
122
  onChangeNotificationSetting,
123
  onCloseClick,
124
  onSaveClick,
125
  saveGlobalSettings,
126
  selectedAttribute,
127
  settings,
128
  tenantCapabilities,
129
  userCapabilities
130
}) => {
131
  const [channelSettings, setChannelSettings] = useState(notificationChannelSettings);
8✔
132
  const [currentInterval, setCurrentInterval] = useState(offlineThresholdSettings.interval);
8✔
133
  const [currentIntervalUnit, setCurrentIntervalUnit] = useState(offlineThresholdSettings.intervalUnit);
8✔
134
  const [intervalErrorText, setIntervalErrorText] = useState('');
8✔
135
  const debouncedOfflineThreshold = useDebounce({ interval: currentInterval, intervalUnit: currentIntervalUnit }, TIMEOUTS.threeSeconds);
8✔
136
  const timer = useRef(false);
8✔
137
  const { classes } = useStyles();
8✔
138
  const { needsDeploymentConfirmation = false } = settings;
8✔
139
  const { canDelta, hasMonitor } = tenantCapabilities;
8✔
140
  const { canManageReleases } = userCapabilities;
8✔
141

142
  useEffect(() => {
8✔
143
    setChannelSettings(notificationChannelSettings);
1✔
144
  }, [notificationChannelSettings]);
145

146
  useEffect(() => {
8✔
147
    setCurrentInterval(offlineThresholdSettings.interval);
1✔
148
    setCurrentIntervalUnit(offlineThresholdSettings.intervalUnit);
1✔
149
  }, [offlineThresholdSettings.interval, offlineThresholdSettings.intervalUnit]);
150

151
  useEffect(() => {
8✔
152
    if (!window.sessionStorage.getItem(settingsKeys.initialized) || !timer.current) {
1!
153
      return;
1✔
154
    }
155
    saveGlobalSettings({ offlineThreshold: debouncedOfflineThreshold }, false, true);
×
156
    // eslint-disable-next-line react-hooks/exhaustive-deps
157
  }, [JSON.stringify(debouncedOfflineThreshold), saveGlobalSettings]);
158

159
  useEffect(() => {
8✔
160
    const initTimer = setTimeout(() => (timer.current = true), TIMEOUTS.fiveSeconds);
1✔
161
    return () => {
1✔
162
      clearTimeout(initTimer);
1✔
163
    };
164
  }, []);
165

166
  const onNotificationSettingsClick = ({ target: { checked } }, channel) => {
8✔
167
    setChannelSettings({ ...channelSettings, channel: { enabled: !checked } });
×
168
    onChangeNotificationSetting(!checked, channel);
×
169
  };
170

171
  const onChangeOfflineIntervalUnit = ({ target: { value } }) => setCurrentIntervalUnit(value);
8✔
172
  const onChangeOfflineInterval = ({ target: { validity, value } }) => {
8✔
173
    if (validity.valid) {
×
174
      setCurrentInterval(value || 1);
×
175
      return setIntervalErrorText('');
×
176
    }
177
    setIntervalErrorText('Please enter a valid number between 1 and 1000.');
×
178
  };
179

180
  const toggleDeploymentConfirmation = () => {
8✔
181
    saveGlobalSettings({ needsDeploymentConfirmation: !needsDeploymentConfirmation });
×
182
  };
183

184
  return (
8✔
185
    <div style={{ maxWidth }} className="margin-top-small">
186
      <div className="flexbox center-aligned">
187
        <h2 className="margin-top-small margin-right-small">Global settings</h2>
188
        <MenderHelpTooltip id={HELPTOOLTIPS.globalSettings.id} placement="top" />
189
      </div>
190
      <IdAttributeSelection attributes={attributes} onCloseClick={onCloseClick} onSaveClick={onSaveClick} selectedAttribute={selectedAttribute} />
191
      {hasReporting && <ReportingLimits />}
16✔
192
      <InputLabel className="margin-top" shrink>
193
        Deployments
194
      </InputLabel>
195
      <div className="clickable flexbox center-aligned" onClick={toggleDeploymentConfirmation}>
196
        <p className="help-content">Require confirmation on deployment creation</p>
197
        <Switch checked={needsDeploymentConfirmation} />
198
      </div>
199
      {canManageReleases && canDelta && <ArtifactGenerationSettings />}
24✔
200
      {isAdmin &&
16!
201
        hasMonitor &&
202
        Object.keys(alertChannels).map(channel => (
203
          <FormControl key={channel}>
×
204
            <InputLabel className="capitalized-start" shrink id={`${channel}-notifications`}>
205
              {channel} notifications
206
            </InputLabel>
207
            <FormControlLabel
208
              control={<Checkbox checked={!channelSettings[channel].enabled} onChange={e => onNotificationSettingsClick(e, channel)} />}
×
209
              label={`Mute ${channel} notifications`}
210
            />
211
            <FormHelperText className="info" component="div">
212
              Mute {channel} notifications for deployment and monitoring issues for all users
213
            </FormHelperText>
214
          </FormControl>
215
        ))}
216

217
      <InputLabel className="margin-top" shrink id="offline-theshold">
218
        Offline threshold
219
      </InputLabel>
220
      <div className={classes.threshold}>
221
        <Select onChange={onChangeOfflineIntervalUnit} value={currentIntervalUnit}>
222
          {offlineThresholds.map(value => (
223
            <MenuItem key={value} value={value}>
24✔
224
              <div className="capitalized-start">{value}</div>
225
            </MenuItem>
226
          ))}
227
        </Select>
228
        <TextField
229
          className={classes.textInput}
230
          type="number"
231
          onChange={onChangeOfflineInterval}
232
          inputProps={{ min: '1', max: '1000' }}
233
          error={!!intervalErrorText}
234
          value={currentInterval}
235
        />
236
      </div>
237
      {!!intervalErrorText && (
8!
238
        <FormHelperText className="warning" component="div">
239
          {intervalErrorText}
240
        </FormHelperText>
241
      )}
242
      <FormHelperText className="info" component="div">
243
        Choose how long a device can go without reporting to the server before it is considered “offline”.
244
      </FormHelperText>
245
    </div>
246
  );
247
};
248

249
export const GlobalSettingsContainer = ({ closeDialog, dialog }) => {
6✔
250
  const dispatch = useDispatch();
5✔
251
  const attributes = useSelector(state => {
5✔
252
    // limit the selection of the available attribute to AVAILABLE_ATTRIBUTE_LIMIT
253
    const attributes = state.devices.filteringAttributes.identityAttributes.slice(0, state.devices.filteringAttributesLimit);
9✔
254
    return attributes.reduce(
9✔
255
      (accu, value) => {
256
        accu.push({ value, label: value, scope: 'identity' });
11✔
257
        return accu;
11✔
258
      },
259
      [
260
        { value: 'name', label: 'Name', scope: 'tags' },
261
        { value: 'id', label: 'Device ID', scope: 'identity' }
262
      ]
263
    );
264
  });
265
  const { hasReporting } = useSelector(getFeatures);
5✔
266
  const { isAdmin } = useSelector(getUserRoles);
5✔
267
  const notificationChannelSettings = useSelector(state => state.monitor.settings.global.channels);
9✔
268
  const offlineThresholdSettings = useSelector(getOfflineThresholdSettings);
5✔
269
  const { attribute: selectedAttribute } = useSelector(getIdAttribute);
5✔
270
  const settings = useSelector(getGlobalSettingsSelector);
5✔
271
  const tenantCapabilities = useSelector(getTenantCapabilities);
5✔
272
  const userCapabilities = useSelector(getUserCapabilities);
5✔
273

274
  const [updatedSettings, setUpdatedSettings] = useState({ ...settings });
5✔
275

276
  useEffect(() => {
5✔
277
    if (!settings) {
1!
278
      dispatch(getGlobalSettings());
×
279
    }
280
    dispatch(getDeviceAttributes());
1✔
281
    // eslint-disable-next-line react-hooks/exhaustive-deps
282
  }, [dispatch, JSON.stringify(settings)]);
283

284
  useEffect(() => {
5✔
285
    setUpdatedSettings(current => ({ ...current, ...settings }));
1✔
286
    // eslint-disable-next-line react-hooks/exhaustive-deps
287
  }, [JSON.stringify(settings)]);
288

289
  const onCloseClick = e => {
5✔
290
    if (dialog) {
×
291
      return closeDialog(e);
×
292
    }
293
  };
294

295
  const saveAttributeSetting = (e, id_attribute) => {
5✔
296
    return dispatch(saveGlobalSettings({ ...updatedSettings, id_attribute }, false, true)).then(() => {
×
297
      if (dialog) {
×
298
        closeDialog(e);
×
299
      }
300
    });
301
  };
302

303
  const onChangeNotificationSetting = useCallback((...args) => dispatch(changeNotificationSetting(...args)), [dispatch]);
5✔
304
  const onSaveGlobalSettings = useCallback((...args) => dispatch(saveGlobalSettings(...args)), [dispatch]);
5✔
305

306
  if (dialog) {
5!
307
    return (
×
308
      <IdAttributeSelection
309
        attributes={attributes}
310
        dialog
311
        onCloseClick={onCloseClick}
312
        onSaveClick={saveAttributeSetting}
313
        selectedAttribute={selectedAttribute}
314
      />
315
    );
316
  }
317
  return (
5✔
318
    <GlobalSettingsDialog
319
      attributes={attributes}
320
      hasReporting={hasReporting}
321
      isAdmin={isAdmin}
322
      notificationChannelSettings={notificationChannelSettings}
323
      offlineThresholdSettings={offlineThresholdSettings}
324
      onChangeNotificationSetting={onChangeNotificationSetting}
325
      onCloseClick={onCloseClick}
326
      onSaveClick={saveAttributeSetting}
327
      saveGlobalSettings={onSaveGlobalSettings}
328
      settings={settings}
329
      selectedAttribute={selectedAttribute}
330
      tenantCapabilities={tenantCapabilities}
331
      userCapabilities={userCapabilities}
332
    />
333
  );
334
};
335
export default GlobalSettingsContainer;
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