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

mendersoftware / gui / 1081664682

22 Nov 2023 02:11PM UTC coverage: 82.798% (-17.2%) from 99.964%
1081664682

Pull #4214

gitlab-ci

tranchitella
fix: Fixed the infinite page redirects when the back button is pressed

Remove the location and navigate from the useLocationParams.setValue callback
dependencies as they change the set function that is presented in other
useEffect dependencies. This happens when the back button is clicked, which
leads to the location changing infinitely.

Changelog: Title
Ticket: MEN-6847
Ticket: MEN-6796

Signed-off-by: Ihor Aleksandrychiev <ihor.aleksandrychiev@northern.tech>
Signed-off-by: Fabio Tranchitella <fabio.tranchitella@northern.tech>
Pull Request #4214: fix: Fixed the infinite page redirects when the back button is pressed

4319 of 6292 branches covered (0.0%)

8332 of 10063 relevant lines covered (82.8%)

191.0 hits per line

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

92.11
/src/js/components/dashboard/widgets/chart-addition.js
1
// Copyright 2020 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, useState } from 'react';
15

16
import { Add as AddIcon } from '@mui/icons-material';
17
import { Button, FormControl, IconButton, InputLabel, ListSubheader, MenuItem, Select, iconButtonClasses, selectClasses } from '@mui/material';
18
import { makeStyles } from 'tss-react/mui';
19

20
import { BENEFITS, chartTypes, emptyChartSelection } from '../../../constants/appConstants';
21
import { toggle } from '../../../helpers';
22
import Confirm from '../../common/confirm';
23
import EnterpriseNotification from '../../common/enterpriseNotification';
24
import { InfoHintContainer } from '../../common/info-hint';
25
import { HELPTOOLTIPS, MenderHelpTooltip } from '../../helptips/helptooltips';
26
import { Header } from './distribution';
27

28
const fontSize = 'smaller';
6✔
29

30
const useStyles = makeStyles()(theme => ({
13✔
31
  additionButton: { fontSize: '1rem', cursor: 'pointer' },
32
  button: { marginLeft: theme.spacing(2), padding: '6px 8px', fontSize },
33
  buttonWrapper: { display: 'flex', justifyContent: 'flex-end', alignContent: 'center' },
34
  iconButton: {
35
    [`&.${iconButtonClasses.root}`]: {
36
      borderRadius: 5,
37
      border: `1px solid ${theme.palette.primary.main}`,
38
      marginRight: theme.spacing(),
39
      '&.selected': {
40
        background: theme.palette.primary.main,
41
        color: theme.palette.background.paper
42
      }
43
    }
44
  },
45
  formWrapper: {
46
    alignItems: 'baseline',
47
    columnGap: theme.spacing(3),
48
    display: 'grid',
49
    fontSize,
50
    gridTemplateColumns: 'max-content 1fr',
51
    gridTemplateRows: 'auto',
52
    rowGap: theme.spacing(0.5),
53
    marginTop: theme.spacing(),
54
    [`.${selectClasses.select}`]: { paddingBottom: theme.spacing(0.5), paddingTop: 0, fontSize }
55
  }
56
}));
57

58
const GroupSelect = ({ groups, selection, setSelection }) => (
6✔
59
  <FormControl className="margin-top-none">
2✔
60
    <InputLabel id="group-select-label">Device group</InputLabel>
61
    <Select labelId="group-select-label" value={selection.group || true} onChange={e => setSelection({ group: e.target.value })}>
1✔
62
      <MenuItem value={true}>
63
        <em>All Devices</em>
64
      </MenuItem>
65
      {Object.keys(groups).map(group => (
66
        <MenuItem key={group} value={group}>
4✔
67
          {group}
68
        </MenuItem>
69
      ))}
70
    </Select>
71
  </FormControl>
72
);
73

74
const themeSpacing = 8;
6✔
75
const basePadding = 2 * themeSpacing;
6✔
76
const getIndentation = level => ({ paddingLeft: basePadding + level * themeSpacing });
6✔
77

78
const SoftwareSelect = ({ selection, setSelection, software }) => (
6✔
79
  <FormControl className="margin-top-none">
2✔
80
    <InputLabel id="software-select-label">Software</InputLabel>
81
    <Select labelId="software-select-label" value={selection.software} onBlur={undefined} onChange={e => setSelection({ software: e.target.value })}>
×
82
      {software.map(({ subheader, title, value, nestingLevel }) =>
83
        subheader ? (
6✔
84
          <ListSubheader key={value} style={getIndentation(nestingLevel)}>
85
            {subheader}
86
          </ListSubheader>
87
        ) : (
88
          <MenuItem key={value} style={getIndentation(nestingLevel)} value={value}>
89
            {title}
90
          </MenuItem>
91
        )
92
      )}
93
    </Select>
94
  </FormControl>
95
);
96

97
const ChartSelect = ({ classes, selection, setSelection }) => (
6✔
98
  <div>
2✔
99
    {Object.values(chartTypes).map(type => {
100
      const { Icon, key } = type;
4✔
101
      return (
4✔
102
        <IconButton
103
          className={`${classes.iconButton} ${selection.chartType === key ? 'selected' : ''}`}
4✔
104
          key={key}
105
          size="small"
106
          onClick={() => setSelection({ chartType: key })}
×
107
        >
108
          <Icon fontSize="small" />
109
        </IconButton>
110
      );
111
    })}
112
  </div>
113
);
114

115
const chartOptions = [
6✔
116
  { key: 'software', title: 'Software', Selector: SoftwareSelect },
117
  { key: 'group', title: 'Device group', Selector: GroupSelect },
118
  { key: 'type', title: 'Display', Selector: ChartSelect }
119
];
120

121
export const ChartEditWidget = ({ groups, onSave, onCancel, selection: selectionProp = {}, software = [] }) => {
6!
122
  const [selection, setSelection] = useState({ ...emptyChartSelection, ...selectionProp });
2✔
123
  const { classes } = useStyles();
2✔
124

125
  const addCurrentSelection = useCallback(
2✔
126
    () => onSave({ ...emptyChartSelection, ...selection, group: typeof selection.group === 'string' ? selection.group : null }),
1!
127
    // eslint-disable-next-line react-hooks/exhaustive-deps
128
    [JSON.stringify(selection), onSave]
129
  );
130

131
  const onSelectionChange = changedSelection => setSelection(current => ({ ...current, ...changedSelection }));
2✔
132

133
  return (
2✔
134
    <div className="widget chart-widget">
135
      <Header chartType={selection.chartType} />
136
      <div className={classes.formWrapper}>
137
        {chartOptions.map(({ key, title, Selector }) => (
138
          <React.Fragment key={key}>
6✔
139
            <div>{title}</div>
140
            <Selector classes={classes} groups={groups} software={software} selection={selection} setSelection={onSelectionChange} />
141
          </React.Fragment>
142
        ))}
143
      </div>
144
      <div className={classes.buttonWrapper}>
145
        <Button className={classes.button} size="small" onClick={onCancel}>
146
          Cancel
147
        </Button>
148
        <Button className={classes.button} size="small" onClick={addCurrentSelection} variant="contained" disabled={!selection}>
149
          Save
150
        </Button>
151
      </div>
152
    </div>
153
  );
154
};
155

156
export const RemovalWidget = ({ onCancel, onClick }) => (
6✔
157
  <div className="widget chart-widget">
×
158
    <Confirm classes="flexbox centered confirmation-overlay" cancel={onCancel} action={onClick} style={{ justifyContent: 'center' }} type="chartRemoval" />
159
  </div>
160
);
161

162
export const WidgetAdditionWidget = ({ onAdditionClick, ...remainder }) => {
6✔
163
  const [adding, setAdding] = useState(false);
1,057✔
164
  const { classes } = useStyles();
1,057✔
165

166
  const addCurrentSelection = selection => {
1,057✔
167
    onAdditionClick(selection);
1✔
168
    setAdding(false);
1✔
169
  };
170

171
  const onCancelClick = () => setAdding(toggle);
1,057✔
172

173
  return adding ? (
1,057✔
174
    <ChartEditWidget {...remainder} onSave={addCurrentSelection} onCancel={onCancelClick} />
175
  ) : (
176
    <div className="widget">
177
      <InfoHintContainer className="" style={{ alignItems: 'end' }}>
178
        <EnterpriseNotification id={BENEFITS.dashboard.id} />
179
        <MenderHelpTooltip id={HELPTOOLTIPS.dashboardWidget.id} />
180
      </InfoHintContainer>
181
      <div className={`flexbox centered muted ${classes.additionButton}`} onClick={() => setAdding(true)}>
1✔
182
        <AddIcon />
183
        <span className={classes.additionButton}>add a widget</span>
184
      </div>
185
    </div>
186
  );
187
};
188

189
export default WidgetAdditionWidget;
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