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

mendersoftware / gui / 914712491

pending completion
914712491

Pull #3798

gitlab-ci

mzedel
refac: refactored signup page to make better use of form capabilities

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #3798: MEN-3530 - refactored forms

4359 of 6322 branches covered (68.95%)

92 of 99 new or added lines in 11 files covered. (92.93%)

1715 existing lines in 159 files now uncovered.

8203 of 9941 relevant lines covered (82.52%)

150.06 hits per line

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

88.52
/src/js/selectors/index.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 { createSelector } from '@reduxjs/toolkit';
15

16
import { mapUserRolesToUiPermissions } from '../actions/userActions';
17
import { PLANS } from '../constants/appConstants';
18
import { DEPLOYMENT_STATES } from '../constants/deploymentConstants';
19
import {
20
  ATTRIBUTE_SCOPES,
21
  DEVICE_ISSUE_OPTIONS,
22
  DEVICE_LIST_MAXIMUM_LENGTH,
23
  DEVICE_ONLINE_CUTOFF,
24
  DEVICE_STATES,
25
  EXTERNAL_PROVIDER,
26
  UNGROUPED_GROUP
27
} from '../constants/deviceConstants';
28
import { rolesByName, twoFAStates, uiPermissionsById } from '../constants/userConstants';
29
import { attributeDuplicateFilter, duplicateFilter, getDemoDeviceAddress as getDemoDeviceAddressHelper, versionCompare } from '../helpers';
30

31
const getAppDocsVersion = state => state.app.docsVersion;
701✔
32
export const getFeatures = state => state.app.features;
7,209✔
33
export const getRolesById = state => state.users.rolesById;
727✔
34
export const getOrganization = state => state.organization.organization;
3,396✔
35
export const getAcceptedDevices = state => state.devices.byStatus.accepted;
3,192✔
36
const getDevicesByStatus = state => state.devices.byStatus;
665✔
37
export const getDevicesById = state => state.devices.byId;
2,695✔
38
const getGroupsById = state => state.devices.groups.byId;
187✔
39
const getSelectedGroup = state => state.devices.groups.selectedGroup;
187✔
40
const getSearchedDevices = state => state.app.searchState.deviceIds;
436✔
41
const getListedDevices = state => state.devices.deviceList.deviceIds;
187✔
42
const getFilteringAttributes = state => state.devices.filteringAttributes;
445✔
43
export const getDeviceFilters = state => state.devices.filters || [];
187!
44
const getFilteringAttributesFromConfig = state => state.devices.filteringAttributesConfig.attributes;
441✔
45
export const getDeviceLimit = state => state.devices.limit;
1,217✔
46
const getDevicesList = state => Object.values(state.devices.byId);
187✔
47
const getOnboarding = state => state.onboarding;
729✔
48
export const getShowHelptips = state => state.users.showHelptips;
3,274✔
49
export const getGlobalSettings = state => state.users.globalSettings;
1,483✔
50
const getIssueCountsByType = state => state.monitor.issueCounts.byType;
443✔
51
export const getReleasesById = state => state.releases.byId;
1,534✔
52
const getListedReleases = state => state.releases.releasesList.releaseIds;
187✔
53
export const getExternalIntegrations = state => state.organization.externalDeviceIntegrations;
187✔
54
const getDeploymentsById = state => state.deployments.byId;
440✔
55
const getDeploymentsByStatus = state => state.deployments.byStatus;
440✔
56
export const getVersionInformation = state => state.app.versionInformation;
1,177✔
57

58
export const getCurrentUser = state => state.users.byId[state.users.currentUser] || {};
1,885✔
59
export const getUserSettings = state => state.users.userSettings;
4,052✔
60
export const getIsPreview = createSelector([getVersionInformation], ({ Integration }) => versionCompare(Integration, 'next') > -1);
187✔
61

62
export const getHas2FA = createSelector(
187✔
63
  [getCurrentUser],
64
  currentUser => currentUser.hasOwnProperty('tfa_status') && currentUser.tfa_status === twoFAStates.enabled
2!
65
);
66

67
export const getDemoDeviceAddress = createSelector([getDevicesList, getOnboarding], (devices, { approach, demoArtifactPort }) => {
187✔
68
  const demoDeviceAddress = `http://${getDemoDeviceAddressHelper(devices, approach)}`;
2✔
69
  return demoArtifactPort ? `${demoDeviceAddress}:${demoArtifactPort}` : demoDeviceAddress;
2!
70
});
71

72
const listItemMapper = (byId, ids, { defaultObject = {}, cutOffSize = DEVICE_LIST_MAXIMUM_LENGTH }) => {
187✔
73
  return ids.slice(0, cutOffSize).reduce((accu, id) => {
472✔
74
    if (id && byId[id]) {
227!
75
      accu.push({ ...defaultObject, ...byId[id] });
227✔
76
    }
77
    return accu;
227✔
78
  }, []);
79
};
80

81
const listTypeDeviceIdMap = {
187✔
82
  deviceList: getListedDevices,
83
  search: getSearchedDevices
84
};
85
const getDeviceMappingDefaults = () => ({ defaultObject: { auth_sets: [] }, cutOffSize: DEVICE_LIST_MAXIMUM_LENGTH });
439✔
86
export const getMappedDevicesList = createSelector(
187✔
87
  [getDevicesById, (state, listType) => listTypeDeviceIdMap[listType](state), getDeviceMappingDefaults],
439✔
88
  listItemMapper
89
);
90

91
export const getDeviceCountsByStatus = createSelector([getDevicesByStatus], byStatus =>
187✔
92
  Object.values(DEVICE_STATES).reduce((accu, state) => {
34✔
93
    accu[state] = byStatus[state].total || 0;
136✔
94
    return accu;
136✔
95
  }, {})
96
);
97

98
export const getSelectedGroupInfo = createSelector(
187✔
99
  [getAcceptedDevices, getGroupsById, getSelectedGroup],
100
  ({ total: acceptedDeviceTotal }, groupsById, selectedGroup) => {
101
    let groupCount = acceptedDeviceTotal;
6✔
102
    let groupFilters = [];
6✔
103
    if (selectedGroup && groupsById[selectedGroup]) {
6✔
104
      groupCount = groupsById[selectedGroup].total;
2✔
105
      groupFilters = groupsById[selectedGroup].filters || [];
2!
106
    }
107
    return { groupCount, selectedGroup, groupFilters };
6✔
108
  }
109
);
110

111
const defaultIdAttribute = Object.freeze({ attribute: 'id', scope: ATTRIBUTE_SCOPES.identity });
187✔
112
export const getIdAttribute = createSelector([getGlobalSettings], ({ id_attribute = { ...defaultIdAttribute } }) => id_attribute);
187✔
113

114
export const getLimitMaxed = createSelector([getAcceptedDevices, getDeviceLimit], ({ total: acceptedDevices = 0 }, deviceLimit) =>
187!
115
  Boolean(deviceLimit && deviceLimit <= acceptedDevices)
6✔
116
);
117

118
export const getFilterAttributes = createSelector(
187✔
119
  [getGlobalSettings, getFilteringAttributes],
120
  ({ previousFilters }, { identityAttributes, inventoryAttributes, systemAttributes, tagAttributes }) => {
121
    const deviceNameAttribute = { key: 'name', value: 'Name', scope: ATTRIBUTE_SCOPES.tags, category: ATTRIBUTE_SCOPES.tags, priority: 1 };
3✔
122
    const deviceIdAttribute = { key: 'id', value: 'Device ID', scope: ATTRIBUTE_SCOPES.identity, category: ATTRIBUTE_SCOPES.identity, priority: 1 };
3✔
123
    const checkInAttribute = { key: 'updated_ts', value: 'Last check-in', scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 };
3✔
124
    const firstRequestAttribute = { key: 'created_ts', value: 'First request', scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 };
3✔
125
    const attributes = [
3✔
UNCOV
126
      ...previousFilters.map(item => ({
×
127
        ...item,
128
        value: deviceIdAttribute.key === item.key ? deviceIdAttribute.value : item.key,
×
129
        category: 'recently used',
130
        priority: 0
131
      })),
132
      deviceNameAttribute,
133
      deviceIdAttribute,
134
      ...identityAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.identity, category: ATTRIBUTE_SCOPES.identity, priority: 1 })),
3✔
135
      ...inventoryAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.inventory, category: ATTRIBUTE_SCOPES.inventory, priority: 2 })),
3✔
UNCOV
136
      ...tagAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.tags, category: ATTRIBUTE_SCOPES.tags, priority: 3 })),
×
137
      checkInAttribute,
138
      firstRequestAttribute,
UNCOV
139
      ...systemAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 }))
×
140
    ];
141
    return attributeDuplicateFilter(attributes, 'key');
3✔
142
  }
143
);
144

145
export const getGroups = createSelector([getGroupsById], groupsById => {
187✔
146
  const groupNames = Object.keys(groupsById).sort();
12✔
147
  const groupedGroups = Object.entries(groupsById)
12✔
148
    .sort((a, b) => a[0].localeCompare(b[0]))
31✔
149
    .reduce(
150
      (accu, [groupname, group]) => {
151
        const name = groupname === UNGROUPED_GROUP.id ? UNGROUPED_GROUP.name : groupname;
31✔
152
        const groupItem = { ...group, groupId: name, name: groupname };
31✔
153
        if (group.filters.length > 0) {
31✔
154
          if (groupname !== UNGROUPED_GROUP.id) {
19✔
155
            accu.dynamic.push(groupItem);
12✔
156
          } else {
157
            accu.ungrouped.push(groupItem);
7✔
158
          }
159
        } else {
160
          accu.static.push(groupItem);
12✔
161
        }
162
        return accu;
31✔
163
      },
164
      { dynamic: [], static: [], ungrouped: [] }
165
    );
166
  return { groupNames, ...groupedGroups };
12✔
167
});
168

169
export const getDeviceTwinIntegrations = createSelector([getExternalIntegrations], integrations =>
187✔
170
  integrations.filter(integration => integration.id && EXTERNAL_PROVIDER[integration.provider]?.deviceTwin)
4✔
171
);
172

173
export const getOfflineThresholdSettings = createSelector([getGlobalSettings], ({ offlineThreshold }) => ({
187✔
174
  interval: offlineThreshold?.interval || DEVICE_ONLINE_CUTOFF.interval,
26✔
175
  intervalUnit: offlineThreshold?.intervalUnit || DEVICE_ONLINE_CUTOFF.intervalName
26✔
176
}));
177

178
export const getOnboardingState = createSelector([getOnboarding, getShowHelptips], ({ complete, progress, showTips, ...remainder }, showHelptips) => ({
187✔
179
  ...remainder,
180
  complete,
181
  progress,
182
  showHelptips,
183
  showTips
184
}));
185

186
export const getDocsVersion = createSelector([getAppDocsVersion, getFeatures], (appDocsVersion, { isHosted }) => {
187✔
187
  // if hosted, use latest docs version
188
  const docsVersion = appDocsVersion ? `${appDocsVersion}/` : 'development/';
41!
189
  return isHosted ? '' : docsVersion;
41✔
190
});
191

192
export const getIsEnterprise = createSelector(
187✔
193
  [getOrganization, getFeatures],
194
  ({ plan = PLANS.os.value }, { isEnterprise, isHosted }) => isEnterprise || (isHosted && plan === PLANS.enterprise.value)
69✔
195
);
196

197
export const getAttributesList = createSelector(
187✔
198
  [getFilteringAttributes, getFilteringAttributesFromConfig],
199
  ({ identityAttributes = [], inventoryAttributes = [] }, { identity = [], inventory = [] }) =>
22!
200
    [...identityAttributes, ...inventoryAttributes, ...identity, ...inventory].filter(duplicateFilter)
11✔
201
);
202

203
export const getUserRoles = createSelector(
187✔
204
  [getCurrentUser, getRolesById, getIsEnterprise, getFeatures, getOrganization],
205
  (currentUser, rolesById, isEnterprise, { isHosted, hasMultitenancy }, { plan = PLANS.os.value }) => {
318✔
206
    const isAdmin = currentUser.roles?.length
375✔
207
      ? currentUser.roles.some(role => role === rolesByName.admin)
44✔
208
      : !(hasMultitenancy || isEnterprise || (isHosted && plan !== PLANS.os.value));
375!
209
    const uiPermissions = isAdmin
375✔
210
      ? mapUserRolesToUiPermissions([rolesByName.admin], rolesById)
211
      : mapUserRolesToUiPermissions(currentUser.roles || [], rolesById);
618✔
212
    return { isAdmin, uiPermissions };
375✔
213
  }
214
);
215

216
const hasPermission = (thing, permission) => Object.values(thing).some(permissions => permissions.includes(permission));
2,792✔
217

218
export const getUserCapabilities = createSelector([getUserRoles], ({ uiPermissions }) => {
187✔
219
  const canManageReleases = hasPermission(uiPermissions.releases, uiPermissionsById.manage.value);
373✔
220
  const canReadReleases = canManageReleases || hasPermission(uiPermissions.releases, uiPermissionsById.read.value);
373✔
221
  const canUploadReleases = canManageReleases || hasPermission(uiPermissions.releases, uiPermissionsById.upload.value);
373✔
222

223
  const canAuditlog = uiPermissions.auditlog.includes(uiPermissionsById.read.value);
373✔
224

225
  const canReadUsers = uiPermissions.userManagement.includes(uiPermissionsById.read.value);
373✔
226
  const canManageUsers = uiPermissions.userManagement.includes(uiPermissionsById.manage.value);
373✔
227

228
  const canReadDevices = hasPermission(uiPermissions.groups, uiPermissionsById.read.value);
373✔
229
  const canWriteDevices = Object.values(uiPermissions.groups).some(
373✔
230
    groupPermissions => groupPermissions.includes(uiPermissionsById.read.value) && groupPermissions.length > 1
64✔
231
  );
232
  const canTroubleshoot = hasPermission(uiPermissions.groups, uiPermissionsById.connect.value);
373✔
233
  const canManageDevices = hasPermission(uiPermissions.groups, uiPermissionsById.manage.value);
373✔
234
  const canConfigure = hasPermission(uiPermissions.groups, uiPermissionsById.configure.value);
373✔
235

236
  const canDeploy = uiPermissions.deployments.includes(uiPermissionsById.deploy.value) || hasPermission(uiPermissions.groups, uiPermissionsById.deploy.value);
373✔
237
  const canReadDeployments = uiPermissions.deployments.includes(uiPermissionsById.read.value);
373✔
238

239
  return {
373✔
240
    canAuditlog,
241
    canConfigure,
242
    canDeploy,
243
    canManageDevices,
244
    canManageReleases,
245
    canManageUsers,
246
    canReadDeployments,
247
    canReadDevices,
248
    canReadReleases,
249
    canReadUsers,
250
    canTroubleshoot,
251
    canUploadReleases,
252
    canWriteDevices,
253
    groupsPermissions: uiPermissions.groups,
254
    releasesPermissions: uiPermissions.releases
255
  };
256
});
257

258
export const getTenantCapabilities = createSelector(
187✔
259
  [getFeatures, getOrganization, getIsEnterprise],
260
  (
261
    {
262
      hasAddons,
263
      hasAuditlogs: isAuditlogEnabled,
264
      hasDeviceConfig: isDeviceConfigEnabled,
265
      hasDeviceConnect: isDeviceConnectEnabled,
266
      hasMonitor: isMonitorEnabled,
267
      isHosted
268
    },
269
    { addons = [], plan },
8✔
270
    isEnterprise
271
  ) => {
272
    const canDelta = isEnterprise || plan === PLANS.professional.value;
39✔
273
    const hasAuditlogs = isAuditlogEnabled && (!isHosted || isEnterprise || plan === PLANS.professional.value);
39!
274
    const hasDeviceConfig = hasAddons || (isDeviceConfigEnabled && (!isHosted || addons.some(addon => addon.name === 'configure' && Boolean(addon.enabled))));
39!
275
    const hasDeviceConnect =
276
      hasAddons || (isDeviceConnectEnabled && (!isHosted || addons.some(addon => addon.name === 'troubleshoot' && Boolean(addon.enabled))));
39!
277
    const hasMonitor = hasAddons || (isMonitorEnabled && (!isHosted || addons.some(addon => addon.name === 'monitor' && Boolean(addon.enabled))));
39!
278
    return {
39✔
279
      canDelta,
280
      canRetry: canDelta,
281
      canSchedule: canDelta,
282
      hasAuditlogs,
283
      hasDeviceConfig,
284
      hasDeviceConnect,
285
      hasFullFiltering: canDelta,
286
      hasMonitor,
287
      isEnterprise
288
    };
289
  }
290
);
291

292
export const getAvailableIssueOptionsByType = createSelector(
187✔
293
  [getFeatures, getTenantCapabilities, getIssueCountsByType],
294
  ({ hasReporting }, { hasFullFiltering, hasMonitor }, issueCounts) =>
295
    Object.values(DEVICE_ISSUE_OPTIONS).reduce((accu, { isCategory, key, needsFullFiltering, needsMonitor, needsReporting, title }) => {
18✔
296
      if (isCategory || (needsReporting && !hasReporting) || (needsFullFiltering && !hasFullFiltering) || (needsMonitor && !hasMonitor)) {
108✔
297
        return accu;
106✔
298
      }
299
      accu[key] = { count: issueCounts[key].filtered, key, title };
2✔
300
      return accu;
2✔
301
    }, {})
302
);
303

304
export const getDeviceTypes = createSelector([getAcceptedDevices, getDevicesById], ({ deviceIds = [] }, devicesById) =>
187!
305
  Object.keys(
1✔
306
    deviceIds.slice(0, 200).reduce((accu, item) => {
307
      const { device_type: deviceTypes = [] } = devicesById[item] ? devicesById[item].attributes : {};
2!
308
      accu = deviceTypes.reduce((deviceTypeAccu, deviceType) => {
2✔
309
        if (deviceType.length > 1) {
2!
310
          deviceTypeAccu[deviceType] = deviceTypeAccu[deviceType] ? deviceTypeAccu[deviceType] + 1 : 1;
2!
311
        }
312
        return deviceTypeAccu;
2✔
313
      }, accu);
314
      return accu;
2✔
315
    }, {})
316
  )
317
);
318

319
const getReleaseMappingDefaults = () => ({});
187✔
320
export const getReleasesList = createSelector([getReleasesById, getListedReleases, getReleaseMappingDefaults], listItemMapper);
187✔
321

322
const relevantDeploymentStates = [DEPLOYMENT_STATES.pending, DEPLOYMENT_STATES.inprogress, DEPLOYMENT_STATES.finished];
187✔
323
export const DEPLOYMENT_CUTOFF = 3;
187✔
324
export const getRecentDeployments = createSelector([getDeploymentsById, getDeploymentsByStatus], (deploymentsById, deploymentsByStatus) =>
187✔
325
  Object.entries(deploymentsByStatus).reduce(
204✔
326
    (accu, [state, byStatus]) => {
327
      if (!relevantDeploymentStates.includes(state) || !byStatus.deploymentIds.length) {
816✔
328
        return accu;
231✔
329
      }
330
      accu[state] = byStatus.deploymentIds
585✔
331
        .reduce((accu, id) => {
332
          if (deploymentsById[id]) {
1,155✔
333
            accu.push(deploymentsById[id]);
1,149✔
334
          }
335
          return accu;
1,155✔
336
        }, [])
337
        .slice(0, DEPLOYMENT_CUTOFF);
338
      accu.total += byStatus.total;
585✔
339
      return accu;
585✔
340
    },
341
    { total: 0 }
342
  )
343
);
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