• 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

71.43
/src/js/components/common/dialogs/physicaldeviceonboarding.js
1
// Copyright 2019 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, { useEffect, useState } from 'react';
15
import { useDispatch, useSelector } from 'react-redux';
16
import { Link } from 'react-router-dom';
17

18
import { InfoOutlined as InfoIcon } from '@mui/icons-material';
19
import { Autocomplete, TextField } from '@mui/material';
20
import { createFilterOptions } from '@mui/material/useAutocomplete';
21

22
import { advanceOnboarding, setOnboardingApproach, setOnboardingDeviceType } from '../../../actions/onboardingActions';
23
import { EXTERNAL_PROVIDER } from '../../../constants/deviceConstants';
24
import { onboardingSteps } from '../../../constants/onboardingConstants';
25
import { getDebConfigurationCode, versionCompare } from '../../../helpers';
26
import {
27
  getCurrentSession,
28
  getFeatures,
29
  getFullVersionInformation,
30
  getIsEnterprise,
31
  getIsPreview,
32
  getOnboardingState,
33
  getOrganization
34
} from '../../../selectors';
35
import { HELPTOOLTIPS, MenderHelpTooltip } from '../../helptips/helptooltips';
36
import CopyCode from '../copy-code';
37
import DocsLink from '../docslink';
38
import { MenderTooltipClickable } from '../mendertooltip';
39

40
const filter = createFilterOptions();
3✔
41

42
const types = [
3✔
43
  { title: 'Raspberry Pi 3', value: 'raspberrypi3' },
44
  { title: 'Raspberry Pi 4', value: 'raspberrypi4' }
45
];
46

47
export const ConvertedImageNote = () => (
3✔
48
  <p>
2✔
49
    We prepared an image, ready for Mender, for you to start with. You can find it in the{' '}
50
    <DocsLink path="get-started/preparation/prepare-a-raspberry-pi-device" title="Prepare a Raspberry Pi device" /> documentation, which also contains
51
    instructions for initial device setup. Once you&apos;re done flashing you can go ahead and proceed to the next step.
52
  </p>
53
);
54

55
const IntegrationsLink = () => (
3✔
56
  <Link to="/settings/integrations" target="_blank">
×
57
    Integration settings
58
  </Link>
59
);
60

61
export const ExternalProviderTip = ({ hasExternalIntegration, integrationProvider }) => (
3✔
62
  <MenderTooltipClickable
6✔
63
    className="clickable flexbox muted"
64
    placement="bottom"
65
    style={{ alignItems: 'end', marginBottom: 3 }}
66
    title={
67
      <div style={{ maxWidth: 350 }}>
68
        {hasExternalIntegration ? (
6✔
69
          <p>
70
            Devices added here will be automatically integrated with the <i>{EXTERNAL_PROVIDER[integrationProvider].title}</i> you set in the{' '}
71
            <IntegrationsLink />.
72
          </p>
73
        ) : (
74
          <p>
75
            To connect your devices with <i>{EXTERNAL_PROVIDER[integrationProvider].title}</i>, go to <IntegrationsLink /> and set up the integration.
76
          </p>
77
        )}
78
      </div>
79
    }
80
  >
81
    <InfoIcon />
82
  </MenderTooltipClickable>
83
);
84

85
export const DeviceTypeSelectionStep = ({
3✔
86
  hasConvertedImage,
87
  hasExternalIntegration,
88
  integrationProvider,
89
  onboardingState,
90
  onSelect,
91
  selection = '',
×
92
  version
93
}) => {
94
  const shouldShowOnboardingTip = !onboardingState.complete && onboardingState.showTips;
5✔
95
  const hasExternalIntegrationSupport = versionCompare(version, '3.2') > -1;
5✔
96
  return (
5✔
97
    <>
98
      <h4>Enter your device type</h4>
99
      <p>Setting this attribute on the device ensures that the device will only receive updates for compatible software releases.</p>
100

101
      <div style={{ display: 'grid', gridTemplateColumns: 'max-content 50px 150px', placeItems: 'end center' }}>
102
        <Autocomplete
103
          id="device-type-selection"
104
          autoSelect
105
          autoHighlight
106
          filterSelectedOptions
107
          freeSolo
108
          getOptionLabel={option => {
109
            // Value selected with enter, right from the input
110
            if (typeof option === 'string') {
9!
111
              return option;
9✔
112
            }
113
            if (option.key === 'custom' && option.value === selection) {
×
114
              return option.value;
×
115
            }
116
            return option.title;
×
117
          }}
118
          handleHomeEndKeys
119
          includeInputInList
120
          filterOptions={(options, params) => {
121
            const filtered = filter(options, params);
×
122
            if (filtered.length !== 1 && params.inputValue !== '') {
×
123
              filtered.push({
×
124
                value: params.inputValue,
125
                key: 'custom',
126
                title: `Use "${params.inputValue}"`
127
              });
128
            }
129
            return filtered;
×
130
          }}
131
          options={types}
132
          onChange={onSelect}
133
          renderInput={params => (
134
            <TextField {...params} label="Device type" placeholder="Choose a device type" InputProps={{ ...params.InputProps }} style={{ marginTop: 0 }} />
6✔
135
          )}
136
          style={{ maxWidth: 300 }}
137
          value={selection}
138
        />
139
        {hasExternalIntegrationSupport && <ExternalProviderTip hasExternalIntegration={hasExternalIntegration} integrationProvider={integrationProvider} />}
10✔
140
        {shouldShowOnboardingTip ? <MenderHelpTooltip id={HELPTOOLTIPS.deviceTypeTip.id} placement="bottom" /> : <div />}
5✔
141
      </div>
142
      {hasConvertedImage && <ConvertedImageNote />}
6✔
143
    </>
144
  );
145
};
146

147
export const InstallationStep = ({ advanceOnboarding, selection, onboardingState, ...remainingProps }) => {
3✔
148
  const codeToCopy = getDebConfigurationCode({ ...remainingProps, deviceType: selection, isOnboarding: !onboardingState.complete });
1✔
149
  return (
1✔
150
    <>
151
      <h4>Log into your device and install the Mender client</h4>
152
      <p>
153
        Copy & paste and run this command <b>on your device</b>:
154
      </p>
155
      <CopyCode code={codeToCopy} onCopy={() => advanceOnboarding(onboardingSteps.DASHBOARD_ONBOARDING_START)} withDescription={true} />
×
156
      <p>This downloads the Mender client on the device, sets the configuration and starts the client.</p>
157
      <p>
158
        Once the client has started, your device will attempt to connect to the server. It will then appear in your Pending devices tab and you can continue.
159
      </p>
160
    </>
161
  );
162
};
163

164
const steps = {
3✔
165
  1: DeviceTypeSelectionStep,
166
  2: InstallationStep
167
};
168

169
const integrationProvider = EXTERNAL_PROVIDER['iot-hub'].provider;
3✔
170

171
export const PhysicalDeviceOnboarding = ({ progress }) => {
3✔
172
  const [selection, setSelection] = useState('');
4✔
173
  const hasExternalIntegration = useSelector(state => {
4✔
174
    const { credentials = {} } = state.organization.externalDeviceIntegrations.find(integration => integration.provider === integrationProvider) ?? {};
8✔
175
    const { [EXTERNAL_PROVIDER['iot-hub'].credentialsAttribute]: azureConnectionString = '' } = credentials;
8✔
176
    return !!azureConnectionString;
8✔
177
  });
178
  const ipAddress = useSelector(state => state.app.hostAddress);
8✔
179
  const isEnterprise = useSelector(getIsEnterprise);
4✔
180
  const { isDemoMode, isHosted } = useSelector(getFeatures);
4✔
181
  const isPreRelease = useSelector(getIsPreview);
4✔
182
  const onboardingState = useSelector(getOnboardingState);
4✔
183
  const { tenant_token: tenantToken } = useSelector(getOrganization);
4✔
184
  const { Integration: version } = useSelector(getFullVersionInformation);
4✔
185
  const { token } = useSelector(getCurrentSession);
4✔
186
  const dispatch = useDispatch();
4✔
187

188
  useEffect(() => {
4✔
189
    dispatch(setOnboardingApproach('physical'));
2✔
190
  }, [dispatch]);
191

192
  const onSelect = (e, deviceType, reason) => {
4✔
193
    if (reason === 'selectOption') {
×
194
      dispatch(setOnboardingDeviceType(deviceType.value));
×
195
      setSelection(deviceType.value);
×
196
    } else if (reason === 'clear') {
×
197
      dispatch(setOnboardingDeviceType(''));
×
198
      setSelection('');
×
199
    }
200
  };
201

202
  const hasConvertedImage = !!selection && selection.length && (selection.startsWith('raspberrypi3') || selection.startsWith('raspberrypi4'));
4!
203

204
  const ComponentToShow = steps[progress];
4✔
205
  return (
4✔
206
    <ComponentToShow
207
      advanceOnboarding={step => dispatch(advanceOnboarding(step))}
×
208
      hasExternalIntegration={hasExternalIntegration}
209
      hasConvertedImage={hasConvertedImage}
210
      integrationProvider={integrationProvider}
211
      ipAddress={ipAddress}
212
      isEnterprise={isEnterprise}
213
      isHosted={isHosted}
214
      isDemoMode={isDemoMode}
215
      isPreRelease={isPreRelease}
216
      onboardingState={onboardingState}
217
      onSelect={onSelect}
218
      selection={selection}
219
      tenantToken={tenantToken}
220
      token={token}
221
      version={version}
222
    />
223
  );
224
};
225

226
export default PhysicalDeviceOnboarding;
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