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

mendersoftware / mender-server / 10423

11 Nov 2025 04:53PM UTC coverage: 74.435% (-0.1%) from 74.562%
10423

push

gitlab-ci

web-flow
Merge pull request #1071 from mendersoftware/dependabot/npm_and_yarn/frontend/main/development-dependencies-92732187be

3868 of 5393 branches covered (71.72%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 2 files covered. (100.0%)

176 existing lines in 95 files now uncovered.

64605 of 86597 relevant lines covered (74.6%)

7.74 hits per line

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

99.6
/frontend/src/js/components/settings/ArtifactGeneration.tsx
1
// Copyright 2022 Northern.tech AS
2✔
2
//
2✔
3
//    Licensed under the Apache License, Version 2.0 (the "License");
2✔
4
//    you may not use this file except in compliance with the License.
2✔
5
//    You may obtain a copy of the License at
2✔
6
//
2✔
7
//        http://www.apache.org/licenses/LICENSE-2.0
2✔
8
//
2✔
9
//    Unless required by applicable law or agreed to in writing, software
2✔
10
//    distributed under the License is distributed on an "AS IS" BASIS,
2✔
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2✔
12
//    See the License for the specific language governing permissions and
2✔
13
//    limitations under the License.
2✔
14
import { useEffect, useMemo, useState } from 'react';
2✔
15
import { Controller, useFormContext } from 'react-hook-form';
2✔
16
import { useDispatch, useSelector } from 'react-redux';
2✔
17

2✔
18
// material ui
2✔
19
import { Button, Checkbox, Divider, Drawer, FormControlLabel, InputAdornment, TextField, Typography, drawerClasses } from '@mui/material';
2✔
20
import { makeStyles } from 'tss-react/mui';
2✔
21

2✔
22
import { DrawerTitle } from '@northern.tech/common-ui/DrawerTitle';
2✔
23
import Form from '@northern.tech/common-ui/forms/Form';
2✔
24
import { TIMEOUTS } from '@northern.tech/store/constants';
2✔
25
import { getDeploymentsConfig, saveDeltaDeploymentsConfig } from '@northern.tech/store/thunks';
2✔
26
import { useDebounce } from '@northern.tech/utils/debouncehook';
2✔
27

2✔
28
const useStyles = makeStyles()(theme => ({
9✔
29
  buttonWrapper: { '&.button-wrapper': { justifyContent: 'start', alignItems: 'center' } },
2✔
30
  drawer: { [`.${drawerClasses.paper}`]: { maxWidth: 'initial' } },
2✔
31
  formWrapper: { display: 'flex', flexDirection: 'column', gap: theme.spacing(2), marginLeft: theme.spacing(2), maxWidth: 300 },
2✔
32
  memoryFormWrapper: { gap: theme.spacing(4), marginLeft: 0 },
2✔
33
  info: { maxWidth: 750 },
2✔
34
  memoryTitle: { alignItems: 'baseline' }
2✔
35
}));
2✔
36

2✔
37
const formDefaults = {
9✔
38
  sourceWindow: 67108864,
2✔
39
  inputWindow: 8388608,
2✔
40
  instructionBuffer: 32768,
2✔
41
  duplicatesWindow: 262144,
2✔
42
  compressionLevel: 0,
2✔
43
  disableChecksum: false,
2✔
44
  disableDecompression: false
2✔
45
};
2✔
46

2✔
47
const numberFields = {
9✔
48
  compressionLevel: { key: 'compressionLevel', title: 'Compression level', hasAdornment: false },
2✔
49
  duplicatesWindow: { key: 'duplicatesWindow', title: 'Compression duplicates size', hasAdornment: true },
2✔
50
  inputWindow: { key: 'inputWindow', title: 'Input window size', hasAdornment: true },
2✔
51
  instructionBuffer: { key: 'instructionBuffer', title: 'Instruction buffer size', hasAdornment: true },
2✔
52
  sourceWindow: { key: 'sourceWindow', title: 'Source buffer size', hasAdornment: true }
2✔
53
};
2✔
54

2✔
55
const NumberInputLimited = ({ limit, onChange, value: propsValue, hasAdornment, ...remainder }) => {
9✔
56
  const [value, setValue] = useState(propsValue);
128✔
57
  const debouncedValue = useDebounce(value, TIMEOUTS.oneSecond);
128✔
58
  const { default: defaultValue, max, min } = limit;
128✔
59

2✔
60
  useEffect(() => {
128✔
61
    setValue(propsValue);
53✔
62
  }, [propsValue]);
2✔
63

2✔
64
  useEffect(() => {
128✔
65
    const minimum = Math.max(min, debouncedValue);
38✔
66
    const allowedValue = Math.min(max ?? minimum, minimum);
38✔
67
    if (allowedValue !== debouncedValue) {
38✔
68
      setValue(allowedValue);
12✔
69
      return;
12✔
70
    }
2✔
71
    onChange(allowedValue);
28✔
72
  }, [debouncedValue, max, min, onChange]);
2✔
73

2✔
74
  return (
128✔
75
    <TextField
2✔
76
      slotProps={{
2✔
77
        input: hasAdornment ? { endAdornment: <InputAdornment position="end">KB</InputAdornment> } : {},
2✔
78
        htmlInput: { step: 1, type: 'numeric', pattern: '[0-9]*', autoComplete: 'off' }
2✔
79
      }}
2✔
80
      error={min || max ? min > value || value > max : false}
2✔
81
      value={value}
2✔
82
      onChange={({ target: { value } }) => setValue(Number(value) || 0)}
10✔
83
      helperText={defaultValue !== undefined && defaultValue !== value ? `Defaults to: ${defaultValue}` : null}
2✔
84
      {...remainder}
2✔
85
    />
2✔
86
  );
2✔
87
};
2✔
88

2✔
89
const ArtifactGenerationSettingsForm = ({ deltaLimits, defaultValues }) => {
9✔
90
  const { classes } = useStyles();
12✔
91
  const { control, reset } = useFormContext();
12✔
92

2✔
93
  const onResetClick = () => reset({ ...defaultValues });
12✔
94

2✔
95
  const numberInputs = useMemo(
12✔
96
    () => [
7✔
97
      { ...numberFields.sourceWindow, ...deltaLimits.sourceWindow },
2✔
98
      { ...numberFields.inputWindow, ...deltaLimits.inputWindow },
2✔
99
      { ...numberFields.instructionBuffer, ...deltaLimits.instructionBuffer },
2✔
100
      { ...numberFields.duplicatesWindow, ...deltaLimits.duplicatesWindow },
2✔
101
      { ...numberFields.compressionLevel, ...deltaLimits.compressionLevel }
2✔
102
    ],
2✔
103
    [deltaLimits.sourceWindow, deltaLimits.inputWindow, deltaLimits.instructionBuffer, deltaLimits.duplicatesWindow, deltaLimits.compressionLevel]
2✔
104
  );
2✔
105

2✔
106
  return (
12✔
107
    <>
2✔
108
      <Typography className={classes.info} variant="body2">
2✔
109
        Before adjusting these parameters, we recommend experimenting with the{' '}
2✔
110
        <a href="https://docs.mender.io/artifact-creation/create-a-delta-update-artifact" target="_blank" rel="noopener noreferrer">
2✔
111
          mender-binary-delta-generator CLI tool
2✔
112
        </a>{' '}
2✔
113
        on your own workstation to find the optimal configuration for your Artifacts. You can learn more about these parameters on the{' '}
2✔
114
        <a href="https://github.com/jmacd/xdelta/blob/wiki/TuningMemoryBudget.md" target="_blank" rel="noopener noreferrer">
2✔
115
          xdelta wiki
2✔
116
        </a>
2✔
117
        .
2✔
118
      </Typography>
2✔
119
      <Typography className="margin-top-small" display="block" variant="subtitle1">
2✔
120
        Compression options
2✔
121
      </Typography>
2✔
122
      <div className={classes.formWrapper}>
2✔
123
        <Controller
2✔
124
          name="disableChecksum"
2✔
125
          control={control}
2✔
126
          render={({ field: { onChange, value } }) => (
2✔
127
            <FormControlLabel
13✔
128
              control={<Checkbox color="primary" checked={!!value} onChange={e => onChange(e.target.checked)} size="small" />}
3✔
129
              label="Disable checksum"
2✔
130
            />
2✔
131
          )}
2✔
132
        />
2✔
133
        <Controller
2✔
134
          name="disableDecompression"
2✔
135
          control={control}
2✔
136
          render={({ field: { onChange, value } }) => (
2✔
137
            <FormControlLabel
12✔
138
              className="margin-top-none"
2✔
UNCOV
139
              control={<Checkbox color="primary" checked={!!value} onChange={e => onChange(e.target.checked)} size="small" />}
2✔
140
              label="Disable external decompression"
2✔
141
            />
2✔
142
          )}
2✔
143
        />
2✔
144
      </div>
2✔
145
      <div className={`flexbox margin-top-small margin-bottom ${classes.memoryTitle}`}>
2✔
146
        <Typography display="block" variant="subtitle1">
2✔
147
          Memory options
2✔
148
        </Typography>
2✔
149
        <Button className="margin-left-small" onClick={onResetClick} variant="text">
2✔
150
          Reset to defaults
2✔
151
        </Button>
2✔
152
      </div>
2✔
153
      <div className={`${classes.formWrapper} ${classes.memoryFormWrapper}`}>
2✔
154
        {numberInputs.map(({ default: defaultValue, key, title, min = 0, max, hasAdornment }) => (
2✔
155
          <Controller
52✔
156
            key={key}
2✔
157
            name={key}
2✔
158
            control={control}
2✔
159
            render={({ field: { onChange, value } }) => (
2✔
160
              <NumberInputLimited limit={{ default: defaultValue, max, min }} hasAdornment={hasAdornment} label={title} value={value} onChange={onChange} />
69✔
161
            )}
2✔
162
          />
2✔
163
        ))}
2✔
164
      </div>
2✔
165
    </>
2✔
166
  );
2✔
167
};
2✔
168

2✔
169
export const ArtifactGenerationSettings = ({ onClose, open }) => {
9✔
170
  const { binaryDelta: deltaConfig = {}, binaryDeltaLimits: deltaLimits = {} } = useSelector(state => state.deployments.config) ?? {};
19!
171
  const dispatch = useDispatch();
10✔
172
  const { classes } = useStyles();
10✔
173

2✔
174
  const initialValues = useMemo(() => ({ ...formDefaults, ...deltaConfig }), [deltaConfig]);
10✔
175

2✔
176
  const defaultValues = useMemo(
10✔
177
    () =>
2✔
178
      Object.entries(deltaLimits).reduce(
8✔
179
        (accu, [key, value]) => {
2✔
180
          if (accu[key]) {
32✔
181
            accu[key] = value.default;
26✔
182
          }
2✔
183
          return accu;
32✔
184
        },
2✔
185
        { ...formDefaults }
2✔
186
      ),
2✔
187
    [deltaLimits]
2✔
188
  );
2✔
189

2✔
190
  useEffect(() => {
10✔
191
    dispatch(getDeploymentsConfig());
6✔
192
  }, [dispatch]);
2✔
193

2✔
194
  const onSubmit = async formValues => {
10✔
195
    try {
4✔
196
      await dispatch(saveDeltaDeploymentsConfig(formValues)).unwrap();
4✔
197
    } catch (error) {
2✔
198
      console.log(error);
3✔
199
      return;
3✔
200
    }
2✔
201
    onClose();
3✔
202
  };
2✔
203

2✔
204
  return (
10✔
205
    <Drawer anchor="right" className={classes.drawer} open={open} onClose={onClose}>
2✔
206
      <DrawerTitle title="Delta artifacts generation configuration" onClose={onClose} />
2✔
207
      <Divider className="margin-bottom" />
2✔
208
      <Form
2✔
209
        classes={{ buttonWrapper: classes.buttonWrapper, cancelButton: '' }}
2✔
210
        defaultValues={defaultValues}
2✔
211
        handleCancel={onClose}
2✔
212
        initialValues={initialValues}
2✔
213
        onSubmit={onSubmit}
2✔
214
        showButtons
2✔
215
        submitLabel="Save"
2✔
216
      >
2✔
217
        <ArtifactGenerationSettingsForm defaultValues={defaultValues} deltaLimits={deltaLimits} />
2✔
218
      </Form>
2✔
219
    </Drawer>
2✔
220
  );
2✔
221
};
2✔
222

2✔
223
export default ArtifactGenerationSettings;
2✔
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