• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / gui / 1493849842

13 Oct 2024 07:39AM UTC coverage: 83.457% (-16.5%) from 99.965%
1493849842

Pull #4531

gitlab-ci

web-flow
chore: Bump send and express in /tests/e2e_tests

Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4531: chore: Bump send and express in /tests/e2e_tests

4486 of 6422 branches covered (69.85%)

8551 of 10246 relevant lines covered (83.46%)

151.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.43
/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, Typography } 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 { DEVICE_ONLINE_CUTOFF } from '../../constants/deviceConstants';
25
import { alertChannels } from '../../constants/monitorConstants';
26
import { settingsKeys } from '../../constants/userConstants';
27
import {
28
  getDeviceIdentityAttributes,
29
  getFeatures,
30
  getGlobalSettings as getGlobalSettingsSelector,
31
  getIdAttribute,
32
  getOfflineThresholdSettings,
33
  getTenantCapabilities,
34
  getUserCapabilities,
35
  getUserRoles
36
} from '../../selectors';
37
import { useDebounce } from '../../utils/debouncehook';
38
import DocsLink from '../common/docslink';
39
import { HELPTOOLTIPS, MenderHelpTooltip } from '../helptips/helptooltips';
40
import ArtifactGenerationSettings from './artifactgeneration';
41
import ReportingLimits from './reportinglimits';
42

43
const maxWidth = 750;
5✔
44

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

58
export const IdAttributeSelection = ({ attributes, dialog = false, onCloseClick, onSaveClick, selectedAttribute = '' }) => {
5!
59
  const [attributeSelection, setAttributeSelection] = useState('name');
3✔
60

61
  useEffect(() => {
3✔
62
    setAttributeSelection(selectedAttribute);
1✔
63
  }, [selectedAttribute]);
64

65
  const changed = selectedAttribute !== attributeSelection;
3✔
66

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

75
  const undoChanges = e => {
3✔
76
    setAttributeSelection(selectedAttribute);
×
77
    if (dialog) {
×
78
      onCloseClick(e);
×
79
    }
80
  };
81

82
  const saveSettings = e => onSaveClick(e, { attribute: attributeSelection, scope: attributes.find(({ value }) => value === attributeSelection).scope });
3✔
83

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

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

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

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

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

159
  useEffect(() => {
4✔
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) => {
4✔
167
    setChannelSettings({ ...channelSettings, channel: { enabled: !checked } });
×
168
    onChangeNotificationSetting(!checked, channel);
×
169
  };
170

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

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

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

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

248
export const GlobalSettingsContainer = ({ closeDialog, dialog }) => {
5✔
249
  const dispatch = useDispatch();
3✔
250
  const attributes = useSelector(getDeviceIdentityAttributes);
3✔
251
  const { hasReporting } = useSelector(getFeatures);
3✔
252
  const { isAdmin } = useSelector(getUserRoles);
3✔
253
  const notificationChannelSettings = useSelector(state => state.monitor.settings.global.channels);
7✔
254
  const offlineThresholdSettings = useSelector(getOfflineThresholdSettings);
3✔
255
  const { attribute: selectedAttribute } = useSelector(getIdAttribute);
3✔
256
  const settings = useSelector(getGlobalSettingsSelector);
3✔
257
  const tenantCapabilities = useSelector(getTenantCapabilities);
3✔
258
  const userCapabilities = useSelector(getUserCapabilities);
3✔
259

260
  const [updatedSettings, setUpdatedSettings] = useState({ ...settings });
3✔
261

262
  useEffect(() => {
3✔
263
    if (!settings) {
1!
264
      dispatch(getGlobalSettings());
×
265
    }
266
    dispatch(getDeviceAttributes());
1✔
267
    // eslint-disable-next-line react-hooks/exhaustive-deps
268
  }, [dispatch, JSON.stringify(settings)]);
269

270
  useEffect(() => {
3✔
271
    setUpdatedSettings(current => ({ ...current, ...settings }));
1✔
272
    // eslint-disable-next-line react-hooks/exhaustive-deps
273
  }, [JSON.stringify(settings)]);
274

275
  const onCloseClick = e => {
3✔
276
    if (dialog) {
×
277
      return closeDialog(e);
×
278
    }
279
  };
280

281
  const onChangeNotificationSetting = useCallback((...args) => dispatch(changeNotificationSetting(...args)), [dispatch]);
3✔
282
  const onSaveGlobalSettings = useCallback((...args) => dispatch(saveGlobalSettings(...args)), [dispatch]);
3✔
283

284
  const saveAttributeSetting = (e, id_attribute) =>
3✔
285
    onSaveGlobalSettings({ ...updatedSettings, id_attribute }, false, true).then(() => {
×
286
      if (dialog) {
×
287
        closeDialog(e);
×
288
      }
289
    });
290

291
  if (dialog) {
3!
292
    return (
×
293
      <IdAttributeSelection
294
        attributes={attributes}
295
        dialog
296
        onCloseClick={onCloseClick}
297
        onSaveClick={saveAttributeSetting}
298
        selectedAttribute={selectedAttribute}
299
      />
300
    );
301
  }
302
  return (
3✔
303
    <GlobalSettingsDialog
304
      attributes={attributes}
305
      hasReporting={hasReporting}
306
      isAdmin={isAdmin}
307
      notificationChannelSettings={notificationChannelSettings}
308
      offlineThresholdSettings={offlineThresholdSettings}
309
      onChangeNotificationSetting={onChangeNotificationSetting}
310
      onCloseClick={onCloseClick}
311
      onSaveClick={saveAttributeSetting}
312
      saveGlobalSettings={onSaveGlobalSettings}
313
      settings={settings}
314
      selectedAttribute={selectedAttribute}
315
      tenantCapabilities={tenantCapabilities}
316
      userCapabilities={userCapabilities}
317
    />
318
  );
319
};
320
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