• 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

85.34
/src/js/actions/onboardingActions.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 Cookies from 'universal-cookie';
15

16
import { DEVICE_STATES } from '../constants/deviceConstants';
17
import {
18
  SET_DEMO_ARTIFACT_PORT,
19
  SET_ONBOARDING_APPROACH,
20
  SET_ONBOARDING_ARTIFACT_INCLUDED,
21
  SET_ONBOARDING_COMPLETE,
22
  SET_ONBOARDING_DEVICE_TYPE,
23
  SET_ONBOARDING_PROGRESS,
24
  SET_SHOW_CREATE_ARTIFACT,
25
  SET_SHOW_ONBOARDING_HELP,
26
  SET_SHOW_ONBOARDING_HELP_DIALOG,
27
  onboardingSteps as onboardingStepNames
28
} from '../constants/onboardingConstants';
29
import { SET_SHOW_HELP } from '../constants/userConstants';
30
import { getDemoDeviceAddress } from '../helpers';
31
import { getUserCapabilities, getUserSettings } from '../selectors';
32
import Tracking from '../tracking';
33
import { applyOnboardingFallbacks, onboardingSteps } from '../utils/onboardingmanager';
34
import { saveUserSettings } from './userActions';
35

36
const cookies = new Cookies();
190✔
37

38
const getCurrentOnboardingState = state => {
190✔
39
  const { showTipsDialog, showCreateArtifactDialog, ...onboardingState } = state.onboarding; // eslint-disable-line no-unused-vars
17✔
40
  const { onboarding = {} } = getUserSettings(state);
17!
41
  return { ...onboardingState, ...onboarding };
17✔
42
};
43

44
const deductOnboardingState = ({ devicesById, devicesByStatus, onboardingState, pastDeployments, releases, userCapabilities, userId }) => {
190✔
45
  const { canDeploy, canManageDevices, canReadDeployments, canReadDevices, canReadReleases, canUploadReleases } = userCapabilities;
2✔
46
  const userCookie = cookies.get(`${userId}-onboarded`);
2✔
47
  const acceptedDevices = devicesByStatus[DEVICE_STATES.accepted].deviceIds;
2✔
48
  const pendingDevices = devicesByStatus[DEVICE_STATES.pending].deviceIds;
2✔
49
  let deviceType = onboardingState.deviceType ?? [];
2✔
50
  deviceType =
2✔
51
    !deviceType.length && acceptedDevices.length && devicesById[acceptedDevices[0]].hasOwnProperty('attributes')
8!
52
      ? devicesById[acceptedDevices[0]].attributes.device_type
53
      : deviceType;
54
  const progress = applyOnboardingFallbacks(onboardingState.progress || determineProgress(acceptedDevices, pendingDevices, releases, pastDeployments));
2✔
55
  return {
2✔
56
    complete: !!(
57
      Boolean(userCookie) ||
19!
58
      onboardingState.complete ||
59
      (acceptedDevices.length > 1 && pendingDevices.length > 0 && releases.length > 1 && pastDeployments.length > 1) ||
60
      (acceptedDevices.length >= 1 && releases.length >= 2 && pastDeployments.length > 2) ||
61
      (acceptedDevices.length >= 1 && pendingDevices.length > 0 && releases.length >= 2 && pastDeployments.length >= 2) ||
62
      Object.keys(onboardingSteps).findIndex(step => step === progress) >= Object.keys(onboardingSteps).length - 1 ||
9✔
63
      onboardingState.disable ||
64
      ![canDeploy, canManageDevices, canReadDeployments, canReadDevices, canReadReleases, canUploadReleases].every(i => i)
6✔
65
    ),
66
    showTips: onboardingState.showTips != null ? onboardingState.showTips : true,
2!
67
    deviceType,
68
    approach: onboardingState.approach || (deviceType.some(type => type.startsWith('qemu')) ? 'virtual' : 'physical'),
2!
69
    artifactIncluded: onboardingState.artifactIncluded,
70
    progress
71
  };
72
};
73

74
export const getOnboardingState = () => (dispatch, getState) => {
190✔
75
  const store = getState();
2✔
76
  let onboardingState = getCurrentOnboardingState(store);
2✔
77
  if (!onboardingState.complete) {
2!
78
    const userId = getState().users.currentUser;
2✔
79
    onboardingState = deductOnboardingState({
2✔
80
      devicesById: store.devices.byId,
81
      devicesByStatus: store.devices.byStatus,
82
      onboardingState,
83
      pastDeployments: store.deployments.byStatus.finished.deploymentIds,
84
      releases: Object.values(store.releases.byId),
85
      userCapabilities: getUserCapabilities(store),
86
      userId
87
    });
88
  }
89
  onboardingState.progress = onboardingState.progress || onboardingStepNames.ONBOARDING_START;
2!
90
  const demoDeviceAddress = `http://${getDemoDeviceAddress(Object.values(store.devices.byId), onboardingState.approach)}`;
2✔
91
  onboardingState.address = store.onboarding.demoArtifactPort ? `${demoDeviceAddress}:${store.onboarding.demoArtifactPort}` : demoDeviceAddress;
2!
92
  const progress = Object.keys(onboardingSteps).findIndex(step => step === onboardingStepNames.ARTIFACT_CREATION_DIALOG);
44✔
93
  const currentProgress = Object.keys(onboardingSteps).findIndex(step => step === onboardingState.progress);
38✔
94
  onboardingState.showArtifactCreation = Math.abs(currentProgress - progress) <= 1;
2✔
95
  if (onboardingState.showArtifactCreation && !onboardingState.complete && onboardingState.showTips && store.users.showHelptips) {
2!
UNCOV
96
    dispatch(setShowCreateArtifactDialog(true));
×
UNCOV
97
    onboardingState.progress = onboardingStepNames.ARTIFACT_CREATION_DIALOG;
×
98
    // although it would be more appropriate to do this in the app component, this happens here because in the app component we would need to track
99
    // redirects, if we want to still allow navigation across the UI while the dialog is visible
UNCOV
100
    if (!window.location.pathname.includes('/ui/releases')) {
×
UNCOV
101
      window.location.replace('/ui/releases');
×
102
    }
103
  }
104
  return Promise.resolve(dispatch(setOnboardingState(onboardingState)));
2✔
105
};
106

107
export const setShowOnboardingHelp =
108
  (show, update = true) =>
190✔
109
  (dispatch, getState) => {
4✔
110
    let tasks = [dispatch({ type: SET_SHOW_ONBOARDING_HELP, show })];
4✔
111
    if (update) {
4✔
112
      const { onboarding = {} } = getUserSettings(getState());
2!
113
      tasks.push(dispatch(saveUserSettings({ onboarding: { ...onboarding, showTips: show }, showHelptips: show })));
2✔
114
      tasks.push(dispatch({ type: SET_SHOW_HELP, show }));
2✔
115
    }
116
    return Promise.all(tasks);
4✔
117
  };
118

119
const setOnboardingProgress = value => dispatch => dispatch({ type: SET_ONBOARDING_PROGRESS, value });
190✔
120

121
export const setOnboardingDeviceType =
122
  (value, update = true) =>
190✔
123
  (dispatch, getState) => {
3✔
124
    let tasks = [dispatch({ type: SET_ONBOARDING_DEVICE_TYPE, value })];
3✔
125
    if (update) {
3✔
126
      const { onboarding = {} } = getUserSettings(getState());
1!
127
      tasks.push(dispatch(saveUserSettings({ onboarding: { ...onboarding, deviceType: value } })));
1✔
128
    }
129
    return Promise.all(tasks);
3✔
130
  };
131

132
export const setOnboardingApproach =
133
  (value, update = true) =>
190✔
134
  (dispatch, getState) => {
7✔
135
    let tasks = [dispatch({ type: SET_ONBOARDING_APPROACH, value })];
7✔
136
    if (update) {
7✔
137
      const { onboarding = {} } = getUserSettings(getState());
5!
138
      tasks.push(dispatch(saveUserSettings({ onboarding: { ...onboarding, approach: value } })));
5✔
139
    }
140
    return Promise.all(tasks);
7✔
141
  };
142

143
const setOnboardingArtifactIncluded = value => dispatch => dispatch({ type: SET_ONBOARDING_ARTIFACT_INCLUDED, value });
190✔
144

145
export const setShowCreateArtifactDialog = show => dispatch => dispatch({ type: SET_SHOW_CREATE_ARTIFACT, show });
190✔
146

147
export const setShowDismissOnboardingTipsDialog = show => dispatch => dispatch({ type: SET_SHOW_ONBOARDING_HELP_DIALOG, show });
190✔
148

149
export const setDemoArtifactPort = port => dispatch => dispatch({ type: SET_DEMO_ARTIFACT_PORT, value: port });
190✔
150

151
export const setOnboardingComplete = val => dispatch => {
190✔
152
  let tasks = [Promise.resolve(dispatch({ type: SET_ONBOARDING_COMPLETE, complete: val }))];
15✔
153
  if (val) {
15✔
154
    tasks.push(Promise.resolve(dispatch({ type: SET_SHOW_ONBOARDING_HELP, show: false })));
4✔
155
    tasks.push(Promise.resolve(dispatch(advanceOnboarding(onboardingStepNames.ONBOARDING_FINISHED))));
4✔
156
  }
157
  return Promise.all(tasks);
15✔
158
};
159

160
export const setOnboardingCanceled = () => dispatch =>
190✔
161
  Promise.all([
1✔
162
    Promise.resolve(dispatch(setShowOnboardingHelp(false))),
163
    Promise.resolve(dispatch(setShowDismissOnboardingTipsDialog(false))),
164
    Promise.resolve(dispatch({ type: SET_ONBOARDING_COMPLETE, complete: true }))
165
  ])
166
    // using ONBOARDING_FINISHED_NOTIFICATION to ensure we get the intended onboarding state set after
167
    // _advancing_ the onboarding progress
168
    .then(() => dispatch(advanceOnboarding(onboardingStepNames.ONBOARDING_FINISHED_NOTIFICATION)))
1✔
169
    // since we can't advance after ONBOARDING_CANCELED, track the step manually here
170
    .then(() => Tracking.event({ category: 'onboarding', action: onboardingSteps.ONBOARDING_CANCELED }));
1✔
171

172
const setOnboardingState = state => dispatch =>
190✔
173
  Promise.all([
2✔
174
    dispatch(setOnboardingComplete(state.complete)),
175
    dispatch(setOnboardingDeviceType(state.deviceType, false)),
176
    dispatch(setOnboardingApproach(state.approach, false)),
177
    dispatch(setOnboardingArtifactIncluded(state.artifactIncluded)),
178
    dispatch(setShowOnboardingHelp(state.showTips, false)),
179
    dispatch(setOnboardingProgress(state.progress)),
180
    dispatch(setShowCreateArtifactDialog(state.showArtifactCreation && !state.complete && state.showTips)),
2!
181
    dispatch(saveUserSettings({ onboarding: state }))
182
  ]);
183

184
export const advanceOnboarding = stepId => (dispatch, getState) => {
190✔
185
  const steps = Object.keys(onboardingSteps);
26✔
186
  const progress = steps.findIndex(step => step === getState().onboarding.progress);
663✔
187
  const stepIndex = steps.findIndex(step => step === stepId);
429✔
188
  // if there is no progress set yet, the onboarding state deduction hasn't happened
189
  // and the subsequent settings persistence would overwrite what we stored
190
  if (progress > stepIndex || getState().onboarding.progress === null) {
26✔
191
    return;
11✔
192
  }
193
  const madeProgress = steps[stepIndex + 1];
15✔
194
  const state = { ...getCurrentOnboardingState(getState()), progress: madeProgress };
15✔
195
  state.complete = stepIndex + 1 >= Object.keys(onboardingSteps).findIndex(step => step === onboardingStepNames.ONBOARDING_FINISHED) ? true : state.complete;
450✔
196
  Tracking.event({ category: 'onboarding', action: stepId });
15✔
197
  return Promise.all([dispatch(setOnboardingProgress(madeProgress)), dispatch(saveUserSettings({ onboarding: state }))]);
15✔
198
};
199

200
const determineProgress = (acceptedDevices, pendingDevices, releases, pastDeployments) => {
190✔
201
  const steps = Object.keys(onboardingSteps);
2✔
202
  let progress = -1;
2✔
203
  progress = pendingDevices.length > 1 ? steps.findIndex(step => step === onboardingStepNames.DEVICES_PENDING_ACCEPTING_ONBOARDING) : progress;
2!
204
  progress = acceptedDevices.length >= 1 ? steps.findIndex(step => step === onboardingStepNames.APPLICATION_UPDATE_REMINDER_TIP) : progress;
18!
205
  progress =
2✔
206
    acceptedDevices.length > 1 && releases.length > 1 ? steps.findIndex(step => step === onboardingStepNames.APPLICATION_UPDATE_REMINDER_TIP) : progress;
9✔
207
  progress =
2✔
208
    acceptedDevices.length > 1 && releases.length > 1 && pastDeployments.length > 1
7✔
209
      ? steps.findIndex(step => step === onboardingStepNames.DEPLOYMENTS_PAST_COMPLETED)
20✔
210
      : progress;
211
  progress =
2✔
212
    acceptedDevices.length >= 1 && releases.length >= 2 && pastDeployments.length > 1
7✔
213
      ? steps.findIndex(step => step === onboardingStepNames.ARTIFACT_MODIFIED_ONBOARDING)
29✔
214
      : progress;
215
  progress =
2✔
216
    acceptedDevices.length >= 1 && releases.length >= 2 && pastDeployments.length > 2
7!
UNCOV
217
      ? steps.findIndex(step => step === onboardingStepNames.ONBOARDING_FINISHED)
×
218
      : progress;
219
  return steps[progress];
2✔
220
};
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