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

mendersoftware / gui / 901187442

pending completion
901187442

Pull #3795

gitlab-ci

mzedel
feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

Ticket: None
Changelog: None
Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3795: feat: increased chances of adopting our intended navigation patterns instead of unsupported browser navigation

4389 of 6365 branches covered (68.96%)

5 of 5 new or added lines in 1 file covered. (100.0%)

1729 existing lines in 165 files now uncovered.

8274 of 10019 relevant lines covered (82.58%)

144.86 hits per line

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

64.13
/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 { Help as HelpIcon, 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 { getDocsVersion, getFeatures, getIsEnterprise, getIsPreview, getOnboardingState, getOrganization, getVersionInformation } from '../../../selectors';
27
import CopyCode from '../copy-code';
28
import { MenderTooltipClickable } from '../mendertooltip';
29

30
const filter = createFilterOptions();
4✔
31

32
const types = [
4✔
33
  { title: 'Raspberry Pi 3', value: 'raspberrypi3' },
34
  { title: 'Raspberry Pi 4', value: 'raspberrypi4' }
35
];
36

37
export const ConvertedImageNote = ({ docsVersion }) => (
4✔
38
  <p>
2✔
39
    We prepared an image, ready for Mender, for you to start with. You can find it in the{' '}
40
    <a href={`https://docs.mender.io/${docsVersion}get-started/preparation/prepare-a-raspberry-pi-device`} target="_blank" rel="noopener noreferrer">
41
      Prepare a Raspberry Pi device
42
    </a>{' '}
43
    documentation, which also contains instructions for initial device setup. Once you&apos;re done flashing you can go ahead and proceed to the next step.
44
  </p>
45
);
46

47
const IntegrationsLink = () => (
4✔
UNCOV
48
  <Link to="/settings/integrations" target="_blank">
×
49
    Integration settings
50
  </Link>
51
);
52

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

77
export const DeviceTypeTip = () => (
4✔
78
  <MenderTooltipClickable
2✔
79
    className="flexbox"
80
    placement="bottom"
81
    style={{ alignItems: 'end' }}
82
    title={
83
      <>
84
        <p>
85
          If you don&apos;t see your exact device on the list, choose <i>Generic ARMv6 or newer</i> to continue the tutorial for now.
86
        </p>
87
        <p>
88
          (Note: if your device is <i>not</i> based on ARMv6 or newer, the tutorial won&apos;t work - instead, go back and use the virtual device)
89
        </p>
90
      </>
91
    }
92
  >
93
    <div className="tooltip help" style={{ marginLeft: 0, position: 'initial' }}>
94
      <HelpIcon />
95
    </div>
96
  </MenderTooltipClickable>
97
);
98

99
export const DeviceTypeSelectionStep = ({
4✔
100
  docsVersion,
101
  hasConvertedImage,
102
  hasExternalIntegration,
103
  integrationProvider,
104
  onboardingState,
105
  onSelect,
106
  selection = '',
×
107
  version
108
}) => {
109
  const shouldShowOnboardingTip = !onboardingState.complete && onboardingState.showTips && onboardingState.showHelptips;
3✔
110
  const hasExternalIntegrationSupport = versionCompare(version, '3.2') > -1;
3✔
111
  return (
3✔
112
    <>
113
      <h4>Enter your device type</h4>
114
      <p>Setting this attribute on the device ensures that the device will only receive updates for compatible software releases.</p>
115

116
      <div style={{ display: 'grid', gridTemplateColumns: 'max-content 50px 150px', justifyItems: 'center' }}>
117
        <Autocomplete
118
          id="device-type-selection"
119
          autoSelect
120
          autoHighlight
121
          filterSelectedOptions
122
          freeSolo
123
          getOptionLabel={option => {
124
            // Value selected with enter, right from the input
125
            if (typeof option === 'string') {
9!
126
              return option;
9✔
127
            }
UNCOV
128
            if (option.key === 'custom' && option.value === selection) {
×
UNCOV
129
              return option.value;
×
130
            }
UNCOV
131
            return option.title;
×
132
          }}
133
          handleHomeEndKeys
134
          includeInputInList
135
          filterOptions={(options, params) => {
UNCOV
136
            const filtered = filter(options, params);
×
UNCOV
137
            if (filtered.length !== 1 && params.inputValue !== '') {
×
UNCOV
138
              filtered.push({
×
139
                value: params.inputValue,
140
                key: 'custom',
141
                title: `Use "${params.inputValue}"`
142
              });
143
            }
UNCOV
144
            return filtered;
×
145
          }}
146
          options={types}
147
          onChange={onSelect}
148
          renderInput={params => (
149
            <TextField {...params} label="Device type" placeholder="Choose a device type" InputProps={{ ...params.InputProps }} style={{ marginTop: 0 }} />
6✔
150
          )}
151
          style={{ maxWidth: 300 }}
152
          value={selection}
153
        />
154
        {shouldShowOnboardingTip ? <DeviceTypeTip /> : <div />}
3✔
155
        {hasExternalIntegrationSupport && <ExternalProviderTip hasExternalIntegration={hasExternalIntegration} integrationProvider={integrationProvider} />}
6✔
156
      </div>
157
      {hasConvertedImage && <ConvertedImageNote docsVersion={docsVersion} />}
4✔
158
    </>
159
  );
160
};
161

162
export const InstallationStep = ({ advanceOnboarding, selection, onboardingState, ...remainingProps }) => {
4✔
163
  const codeToCopy = getDebConfigurationCode({ ...remainingProps, deviceType: selection, isOnboarding: !onboardingState.complete });
1✔
164
  return (
1✔
165
    <>
166
      <h4>Log into your device and install the Mender client</h4>
167
      <p>
168
        Copy & paste and run this command <b>on your device</b>:
169
      </p>
UNCOV
170
      <CopyCode code={codeToCopy} onCopy={() => advanceOnboarding(onboardingSteps.DASHBOARD_ONBOARDING_START)} withDescription={true} />
×
171
      <p>This downloads the Mender client on the device, sets the configuration and starts the client.</p>
172
      <p>
173
        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.
174
      </p>
175
    </>
176
  );
177
};
178

179
const steps = {
4✔
180
  1: DeviceTypeSelectionStep,
181
  2: InstallationStep
182
};
183

184
const integrationProvider = EXTERNAL_PROVIDER['iot-hub'].provider;
4✔
185

186
export const PhysicalDeviceOnboarding = ({ progress }) => {
4✔
187
  const [selection, setSelection] = useState('');
2✔
188
  const hasExternalIntegration = useSelector(state => {
2✔
189
    const { credentials = {} } = state.organization.externalDeviceIntegrations.find(integration => integration.provider === integrationProvider) ?? {};
2✔
190
    const { [EXTERNAL_PROVIDER['iot-hub'].credentialsAttribute]: azureConnectionString = '' } = credentials;
2✔
191
    return !!azureConnectionString;
2✔
192
  });
193
  const docsVersion = useSelector(getDocsVersion);
2✔
194
  const ipAddress = useSelector(state => state.app.hostAddress);
2✔
195
  const isEnterprise = useSelector(getIsEnterprise);
2✔
196
  const { isDemoMode, isHosted } = useSelector(getFeatures);
2✔
197
  const isPreRelease = useSelector(getIsPreview);
2✔
198
  const onboardingState = useSelector(getOnboardingState);
2✔
199
  const { tenant_token: tenantToken } = useSelector(getOrganization);
2✔
200
  const { Integration: version } = useSelector(getVersionInformation);
2✔
201
  const dispatch = useDispatch();
2✔
202

203
  useEffect(() => {
2✔
204
    dispatch(setOnboardingApproach('physical'));
2✔
205
  }, []);
206

207
  const onSelect = (e, deviceType, reason) => {
2✔
UNCOV
208
    if (reason === 'selectOption') {
×
UNCOV
209
      dispatch(setOnboardingDeviceType(deviceType.value));
×
UNCOV
210
      setSelection(deviceType.value);
×
UNCOV
211
    } else if (reason === 'clear') {
×
UNCOV
212
      dispatch(setOnboardingDeviceType(''));
×
UNCOV
213
      setSelection('');
×
214
    }
215
  };
216

217
  const hasConvertedImage = !!selection && selection.length && (selection.startsWith('raspberrypi3') || selection.startsWith('raspberrypi4'));
2!
218

219
  const ComponentToShow = steps[progress];
2✔
220
  return (
2✔
221
    <ComponentToShow
UNCOV
222
      advanceOnboarding={step => dispatch(advanceOnboarding(step))}
×
223
      hasExternalIntegration={hasExternalIntegration}
224
      docsVersion={docsVersion}
225
      hasConvertedImage={hasConvertedImage}
226
      integrationProvider={integrationProvider}
227
      ipAddress={ipAddress}
228
      isEnterprise={isEnterprise}
229
      isHosted={isHosted}
230
      isDemoMode={isDemoMode}
231
      isPreRelease={isPreRelease}
232
      onboardingState={onboardingState}
233
      onSelect={onSelect}
234
      selection={selection}
235
      tenantToken={tenantToken}
236
      version={version}
237
    />
238
  );
239
};
240

241
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