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

mendersoftware / gui / 1113439055

19 Dec 2023 09:01PM UTC coverage: 82.752% (-17.2%) from 99.964%
1113439055

Pull #4258

gitlab-ci

mender-test-bot
chore: Types update

Signed-off-by: Mender Test Bot <mender@northern.tech>
Pull Request #4258: chore: Types update

4326 of 6319 branches covered (0.0%)

8348 of 10088 relevant lines covered (82.75%)

189.39 hits per line

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

97.21
/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 { defaultReports } from '../actions/deviceActions';
17
import { mapUserRolesToUiPermissions } from '../actions/userActions';
18
import { PLANS } from '../constants/appConstants';
19
import { DEPLOYMENT_STATES } from '../constants/deploymentConstants';
20
import {
21
  ALL_DEVICES,
22
  ATTRIBUTE_SCOPES,
23
  DEVICE_ISSUE_OPTIONS,
24
  DEVICE_LIST_MAXIMUM_LENGTH,
25
  DEVICE_ONLINE_CUTOFF,
26
  DEVICE_STATES,
27
  EXTERNAL_PROVIDER,
28
  UNGROUPED_GROUP
29
} from '../constants/deviceConstants';
30
import { READ_STATES, rolesByName, twoFAStates, uiPermissionsById } from '../constants/userConstants';
31
import { attributeDuplicateFilter, duplicateFilter, getDemoDeviceAddress as getDemoDeviceAddressHelper, versionCompare } from '../helpers';
32
import { onboardingSteps } from '../utils/onboardingmanager';
33

34
const getAppDocsVersion = state => state.app.docsVersion;
1,239✔
35
export const getFeatures = state => state.app.features;
17,604✔
36
export const getTooltipsById = state => state.users.tooltips.byId;
2,348✔
37
export const getRolesById = state => state.users.rolesById;
1,552✔
38
export const getOrganization = state => state.organization.organization;
6,302✔
39
export const getAcceptedDevices = state => state.devices.byStatus.accepted;
6,384✔
40
export const getCurrentSession = state => state.users.currentSession;
2,882✔
41
const getDevicesByStatus = state => state.devices.byStatus;
1,288✔
42
export const getDevicesById = state => state.devices.byId;
24,692✔
43
export const getDeviceReports = state => state.devices.reports;
3,158✔
44
export const getGroupsById = state => state.devices.groups.byId;
2,716✔
45
const getSelectedGroup = state => state.devices.groups.selectedGroup;
183✔
46
const getSearchedDevices = state => state.app.searchState.deviceIds;
962✔
47
const getListedDevices = state => state.devices.deviceList.deviceIds;
183✔
48
const getFilteringAttributes = state => state.devices.filteringAttributes;
1,103✔
49
export const getDeviceFilters = state => state.devices.filters || [];
184!
50
const getFilteringAttributesFromConfig = state => state.devices.filteringAttributesConfig.attributes;
1,070✔
51
export const getSortedFilteringAttributes = createSelector([getFilteringAttributes], filteringAttributes => ({
183✔
52
  ...filteringAttributes,
53
  identityAttributes: [...filteringAttributes.identityAttributes, 'id']
54
}));
55
export const getDeviceLimit = state => state.devices.limit;
1,801✔
56
const getDevicesList = state => Object.values(state.devices.byId);
183✔
57
const getOnboarding = state => state.onboarding;
1,424✔
58
export const getGlobalSettings = state => state.users.globalSettings;
3,375✔
59
const getIssueCountsByType = state => state.monitor.issueCounts.byType;
1,077✔
60
const getSelectedReleaseId = state => state.releases.selectedRelease;
183✔
61
export const getReleasesById = state => state.releases.byId;
1,484✔
62
export const getReleaseTags = state => state.releases.tags;
268✔
63
export const getReleaseListState = state => state.releases.releasesList;
857✔
64
const getListedReleases = state => state.releases.releasesList.releaseIds;
183✔
65
export const getUpdateTypes = state => state.releases.updateTypes;
183✔
66
export const getExternalIntegrations = state => state.organization.externalDeviceIntegrations;
183✔
67
const getDeploymentsById = state => state.deployments.byId;
2,270✔
68
export const getDeploymentsByStatus = state => state.deployments.byStatus;
1,820✔
69
const getSelectedDeploymentDeviceIds = state => state.deployments.selectedDeviceIds;
183✔
70
export const getDeploymentsSelectionState = state => state.deployments.selectionState;
1,977✔
71
export const getFullVersionInformation = state => state.app.versionInformation;
997✔
72
const getCurrentUserId = state => state.users.currentUser;
2,670✔
73
const getUsersById = state => state.users.byId;
1,600✔
74
export const getCurrentUser = createSelector([getUsersById, getCurrentUserId], (usersById, userId) => usersById[userId] ?? {});
183✔
75
export const getUserSettings = state => state.users.userSettings;
9,671✔
76

77
export const getVersionInformation = createSelector([getFullVersionInformation, getFeatures], ({ Integration, ...remainder }, { isHosted }) =>
183✔
78
  isHosted && Integration !== 'next' ? remainder : { ...remainder, Integration }
9!
79
);
80
export const getIsPreview = createSelector([getFullVersionInformation], ({ Integration }) => versionCompare(Integration, 'next') > -1);
183✔
81

82
export const getShowHelptips = createSelector([getTooltipsById], tooltips =>
183✔
83
  Object.values(tooltips).reduce((accu, { readState }) => accu || readState === READ_STATES.unread, false)
2!
84
);
85

86
export const getMappedDeploymentSelection = createSelector(
183✔
87
  [getDeploymentsSelectionState, (_, deploymentsState) => deploymentsState, getDeploymentsById],
1,177✔
88
  (selectionState, deploymentsState, deploymentsById) => {
89
    const { selection = [] } = selectionState[deploymentsState] ?? {};
1,146!
90
    return selection.reduce((accu, id) => {
1,146✔
91
      if (deploymentsById[id]) {
2,096!
92
        accu.push(deploymentsById[id]);
2,096✔
93
      }
94
      return accu;
2,096✔
95
    }, []);
96
  }
97
);
98

99
export const getDeploymentRelease = createSelector(
183✔
100
  [getDeploymentsById, getDeploymentsSelectionState, getReleasesById],
101
  (deploymentsById, { selectedId }, releasesById) => {
102
    const deployment = deploymentsById[selectedId] || {};
5✔
103
    return deployment.artifact_name && releasesById[deployment.artifact_name] ? releasesById[deployment.artifact_name] : { device_types_compatible: [] };
5✔
104
  }
105
);
106

107
export const getSelectedDeploymentData = createSelector(
183✔
108
  [getDeploymentsById, getDeploymentsSelectionState, getDevicesById, getSelectedDeploymentDeviceIds],
109
  (deploymentsById, { selectedId }, devicesById, selectedDeviceIds) => {
110
    const deployment = deploymentsById[selectedId] ?? {};
5✔
111
    const { devices = {} } = deployment;
5✔
112
    return {
5✔
113
      deployment,
114
      selectedDevices: selectedDeviceIds.map(deviceId => ({ ...devicesById[deviceId], ...devices[deviceId] }))
3✔
115
    };
116
  }
117
);
118

119
export const getHas2FA = createSelector(
183✔
120
  [getCurrentUser],
121
  currentUser => currentUser.hasOwnProperty('tfa_status') && currentUser.tfa_status === twoFAStates.enabled
5✔
122
);
123

124
export const getDemoDeviceAddress = createSelector([getDevicesList, getOnboarding], (devices, { approach, demoArtifactPort }) => {
183✔
125
  const demoDeviceAddress = `http://${getDemoDeviceAddressHelper(devices, approach)}`;
3✔
126
  return demoArtifactPort ? `${demoDeviceAddress}:${demoArtifactPort}` : demoDeviceAddress;
3!
127
});
128

129
export const getDeviceReportsForUser = createSelector(
183✔
130
  [getUserSettings, getCurrentUserId, getGlobalSettings, getDevicesById],
131
  ({ reports }, currentUserId, globalSettings, devicesById) => {
132
    return reports || globalSettings[`${currentUserId}-reports`] || (Object.keys(devicesById).length ? defaultReports : []);
27✔
133
  }
134
);
135

136
const listItemMapper = (byId, ids, { defaultObject = {}, cutOffSize = DEVICE_LIST_MAXIMUM_LENGTH }) => {
183✔
137
  return ids.slice(0, cutOffSize).reduce((accu, id) => {
1,029✔
138
    if (id && byId[id]) {
329!
139
      accu.push({ ...defaultObject, ...byId[id] });
329✔
140
    }
141
    return accu;
329✔
142
  }, []);
143
};
144

145
const listTypeDeviceIdMap = {
183✔
146
  deviceList: getListedDevices,
147
  search: getSearchedDevices
148
};
149
const getDeviceMappingDefaults = () => ({ defaultObject: { auth_sets: [] }, cutOffSize: DEVICE_LIST_MAXIMUM_LENGTH });
989✔
150
export const getMappedDevicesList = createSelector(
183✔
151
  [getDevicesById, (state, listType) => listTypeDeviceIdMap[listType](state), getDeviceMappingDefaults],
989✔
152
  listItemMapper
153
);
154

155
export const getDeviceCountsByStatus = createSelector([getDevicesByStatus], byStatus =>
183✔
156
  Object.values(DEVICE_STATES).reduce((accu, state) => {
407✔
157
    accu[state] = byStatus[state].total || 0;
1,628✔
158
    return accu;
1,628✔
159
  }, {})
160
);
161

162
export const getDeviceById = createSelector([getDevicesById, (_, deviceId) => deviceId], (devicesById, deviceId = '') => devicesById[deviceId] ?? {});
1,457✔
163

164
export const getDeviceConfigDeployment = createSelector([getDeviceById, getDeploymentsById], (device, deploymentsById) => {
183✔
165
  const { config = {} } = device;
7✔
166
  const { deployment_id: configDeploymentId } = config;
7✔
167
  const deviceConfigDeployment = deploymentsById[configDeploymentId] || {};
7✔
168
  return { device, deviceConfigDeployment };
7✔
169
});
170

171
export const getSelectedGroupInfo = createSelector(
183✔
172
  [getAcceptedDevices, getGroupsById, getSelectedGroup],
173
  ({ total: acceptedDeviceTotal }, groupsById, selectedGroup) => {
174
    let groupCount = acceptedDeviceTotal;
7✔
175
    let groupFilters = [];
7✔
176
    if (selectedGroup && groupsById[selectedGroup]) {
7✔
177
      groupCount = groupsById[selectedGroup].total;
2✔
178
      groupFilters = groupsById[selectedGroup].filters.map(filter => ({ ...filter, isGroupFilter: true })) || [];
2!
179
    }
180
    return { groupCount, selectedGroup, groupFilters };
7✔
181
  }
182
);
183

184
const defaultIdAttribute = Object.freeze({ attribute: 'id', scope: ATTRIBUTE_SCOPES.identity });
183✔
185
export const getIdAttribute = createSelector([getGlobalSettings], ({ id_attribute = { ...defaultIdAttribute } }) => id_attribute);
183✔
186

187
export const getLimitMaxed = createSelector([getAcceptedDevices, getDeviceLimit], ({ total: acceptedDevices = 0 }, deviceLimit) =>
183!
188
  Boolean(deviceLimit && deviceLimit <= acceptedDevices)
7✔
189
);
190

191
export const getFilterAttributes = createSelector(
183✔
192
  [getGlobalSettings, getFilteringAttributes],
193
  ({ previousFilters }, { identityAttributes, inventoryAttributes, systemAttributes, tagAttributes }) => {
194
    const deviceNameAttribute = { key: 'name', value: 'Name', scope: ATTRIBUTE_SCOPES.tags, category: ATTRIBUTE_SCOPES.tags, priority: 1 };
3✔
195
    const deviceIdAttribute = { key: 'id', value: 'Device ID', scope: ATTRIBUTE_SCOPES.identity, category: ATTRIBUTE_SCOPES.identity, priority: 1 };
3✔
196
    const checkInAttribute = { key: 'check_in_time', value: 'Latest activity', scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 };
3✔
197
    const updateAttribute = { ...checkInAttribute, key: 'updated_ts', value: 'Last inventory update' };
3✔
198
    const firstRequestAttribute = { key: 'created_ts', value: 'First request', scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 };
3✔
199
    const attributes = [
3✔
200
      ...previousFilters.map(item => ({
×
201
        ...item,
202
        value: deviceIdAttribute.key === item.key ? deviceIdAttribute.value : item.key,
×
203
        category: 'recently used',
204
        priority: 0
205
      })),
206
      deviceNameAttribute,
207
      deviceIdAttribute,
208
      ...identityAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.identity, category: ATTRIBUTE_SCOPES.identity, priority: 1 })),
3✔
209
      ...inventoryAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.inventory, category: ATTRIBUTE_SCOPES.inventory, priority: 2 })),
3✔
210
      ...tagAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.tags, category: ATTRIBUTE_SCOPES.tags, priority: 3 })),
×
211
      checkInAttribute,
212
      updateAttribute,
213
      firstRequestAttribute,
214
      ...systemAttributes.map(item => ({ key: item, value: item, scope: ATTRIBUTE_SCOPES.system, category: ATTRIBUTE_SCOPES.system, priority: 4 }))
×
215
    ];
216
    return attributeDuplicateFilter(attributes, 'key');
3✔
217
  }
218
);
219

220
const getFilteringAttributesLimit = state => state.devices.filteringAttributesLimit;
183✔
221

222
export const getDeviceIdentityAttributes = createSelector(
183✔
223
  [getFilteringAttributes, getFilteringAttributesLimit],
224
  ({ identityAttributes }, filteringAttributesLimit) => {
225
    // limit the selection of the available attribute to AVAILABLE_ATTRIBUTE_LIMIT
226
    const attributes = identityAttributes.slice(0, filteringAttributesLimit);
2✔
227
    return attributes.reduce(
2✔
228
      (accu, value) => {
229
        accu.push({ value, label: value, scope: 'identity' });
3✔
230
        return accu;
3✔
231
      },
232
      [
233
        { value: 'name', label: 'Name', scope: 'tags' },
234
        { value: 'id', label: 'Device ID', scope: 'identity' }
235
      ]
236
    );
237
  }
238
);
239

240
// eslint-disable-next-line no-unused-vars
241
export const getGroupsByIdWithoutUngrouped = createSelector([getGroupsById], ({ [UNGROUPED_GROUP.id]: ungrouped, ...groups }) => groups);
183✔
242

243
export const getGroups = createSelector([getGroupsById], groupsById => {
183✔
244
  const groupNames = Object.keys(groupsById).sort();
11✔
245
  const groupedGroups = Object.entries(groupsById)
11✔
246
    .sort((a, b) => a[0].localeCompare(b[0]))
35✔
247
    .reduce(
248
      (accu, [groupname, group]) => {
249
        const name = groupname === UNGROUPED_GROUP.id ? UNGROUPED_GROUP.name : groupname;
30✔
250
        const groupItem = { ...group, groupId: name, name: groupname };
30✔
251
        if (group.filters.length > 0) {
30✔
252
          if (groupname !== UNGROUPED_GROUP.id) {
19✔
253
            accu.dynamic.push(groupItem);
11✔
254
          } else {
255
            accu.ungrouped.push(groupItem);
8✔
256
          }
257
        } else {
258
          accu.static.push(groupItem);
11✔
259
        }
260
        return accu;
30✔
261
      },
262
      { dynamic: [], static: [], ungrouped: [] }
263
    );
264
  return { groupNames, ...groupedGroups };
11✔
265
});
266

267
export const getDeviceTwinIntegrations = createSelector([getExternalIntegrations], integrations =>
183✔
268
  integrations.filter(integration => integration.id && EXTERNAL_PROVIDER[integration.provider]?.deviceTwin)
4✔
269
);
270

271
export const getOfflineThresholdSettings = createSelector([getGlobalSettings], ({ offlineThreshold }) => ({
183✔
272
  interval: offlineThreshold?.interval || DEVICE_ONLINE_CUTOFF.interval,
18✔
273
  intervalUnit: offlineThreshold?.intervalUnit || DEVICE_ONLINE_CUTOFF.intervalName
18✔
274
}));
275

276
export const getOnboardingState = createSelector([getOnboarding, getUserSettings], ({ complete, progress, showTips, ...remainder }, { onboarding = {} }) => ({
183!
277
  ...remainder,
278
  ...onboarding,
279
  complete: onboarding.complete || complete,
126✔
280
  progress:
281
    Object.keys(onboardingSteps).findIndex(step => step === progress) > Object.keys(onboardingSteps).findIndex(step => step === onboarding.progress)
1,027✔
282
      ? progress
283
      : onboarding.progress,
284
  showTips: !onboarding.showTips ? onboarding.showTips : showTips
64!
285
}));
286

287
export const getTooltipsState = createSelector([getTooltipsById, getUserSettings], (byId, { tooltips = {} }) =>
183✔
288
  Object.entries(byId).reduce(
44✔
289
    (accu, [id, value]) => {
290
      accu[id] = { ...accu[id], ...value };
×
291
      return accu;
×
292
    },
293
    { ...tooltips }
294
  )
295
);
296

297
export const getDocsVersion = createSelector([getAppDocsVersion, getFeatures], (appDocsVersion, { isHosted }) => {
183✔
298
  // if hosted, use latest docs version
299
  const docsVersion = appDocsVersion ? `${appDocsVersion}/` : 'development/';
37!
300
  return isHosted ? '' : docsVersion;
37✔
301
});
302

303
export const getIsEnterprise = createSelector(
183✔
304
  [getOrganization, getFeatures],
305
  ({ plan = PLANS.os.id }, { isEnterprise, isHosted }) => isEnterprise || (isHosted && plan === PLANS.enterprise.id)
74✔
306
);
307

308
export const getAttributesList = createSelector(
183✔
309
  [getFilteringAttributes, getFilteringAttributesFromConfig],
310
  ({ identityAttributes = [], inventoryAttributes = [] }, { identity = [], inventory = [] }) =>
34!
311
    [...identityAttributes, ...inventoryAttributes, ...identity, ...inventory].filter(duplicateFilter)
17✔
312
);
313

314
export const getRolesList = createSelector([getRolesById], rolesById => Object.entries(rolesById).map(([id, role]) => ({ id, ...role })));
183✔
315

316
export const getUserRoles = createSelector(
183✔
317
  [getCurrentUser, getRolesById, getIsEnterprise, getFeatures, getOrganization],
318
  (currentUser, rolesById, isEnterprise, { isHosted, hasMultitenancy }, { plan = PLANS.os.id }) => {
3✔
319
    const isAdmin = currentUser.roles?.length
60✔
320
      ? currentUser.roles.some(role => role === rolesByName.admin)
58✔
321
      : !(hasMultitenancy || isEnterprise || (isHosted && plan !== PLANS.os.id));
6!
322
    const uiPermissions = isAdmin
60!
323
      ? mapUserRolesToUiPermissions([rolesByName.admin], rolesById)
324
      : mapUserRolesToUiPermissions(currentUser.roles || [], rolesById);
×
325
    return { isAdmin, uiPermissions };
60✔
326
  }
327
);
328

329
const hasPermission = (thing, permission) => Object.values(thing).some(permissions => permissions.includes(permission));
265✔
330

331
export const getUserCapabilities = createSelector([getUserRoles], ({ uiPermissions }) => {
183✔
332
  const canManageReleases = hasPermission(uiPermissions.releases, uiPermissionsById.manage.value);
53✔
333
  const canReadReleases = canManageReleases || hasPermission(uiPermissions.releases, uiPermissionsById.read.value);
53!
334
  const canUploadReleases = canManageReleases || hasPermission(uiPermissions.releases, uiPermissionsById.upload.value);
53!
335

336
  const canAuditlog = uiPermissions.auditlog.includes(uiPermissionsById.read.value);
53✔
337

338
  const canReadUsers = uiPermissions.userManagement.includes(uiPermissionsById.read.value);
53✔
339
  const canManageUsers = uiPermissions.userManagement.includes(uiPermissionsById.manage.value);
53✔
340

341
  const canReadDevices = hasPermission(uiPermissions.groups, uiPermissionsById.read.value);
53✔
342
  const canWriteDevices = Object.values(uiPermissions.groups).some(
53✔
343
    groupPermissions => groupPermissions.includes(uiPermissionsById.read.value) && groupPermissions.length > 1
53✔
344
  );
345
  const canTroubleshoot = hasPermission(uiPermissions.groups, uiPermissionsById.connect.value);
53✔
346
  const canManageDevices = hasPermission(uiPermissions.groups, uiPermissionsById.manage.value);
53✔
347
  const canConfigure = hasPermission(uiPermissions.groups, uiPermissionsById.configure.value);
53✔
348

349
  const canDeploy = uiPermissions.deployments.includes(uiPermissionsById.deploy.value) || hasPermission(uiPermissions.groups, uiPermissionsById.deploy.value);
53!
350
  const canReadDeployments = uiPermissions.deployments.includes(uiPermissionsById.read.value);
53✔
351

352
  return {
53✔
353
    canAuditlog,
354
    canConfigure,
355
    canDeploy,
356
    canManageDevices,
357
    canManageReleases,
358
    canManageUsers,
359
    canReadDeployments,
360
    canReadDevices,
361
    canReadReleases,
362
    canReadUsers,
363
    canTroubleshoot,
364
    canUploadReleases,
365
    canWriteDevices,
366
    groupsPermissions: uiPermissions.groups,
367
    releasesPermissions: uiPermissions.releases
368
  };
369
});
370

371
export const getTenantCapabilities = createSelector(
183✔
372
  [getFeatures, getOrganization, getIsEnterprise],
373
  (
374
    {
375
      hasAddons,
376
      hasAuditlogs: isAuditlogEnabled,
377
      hasDeviceConfig: isDeviceConfigEnabled,
378
      hasDeviceConnect: isDeviceConnectEnabled,
379
      hasMonitor: isMonitorEnabled,
380
      isHosted
381
    },
382
    { addons = [], plan = PLANS.os.id },
4✔
383
    isEnterprise
384
  ) => {
385
    const canDelta = isEnterprise || plan === PLANS.professional.id;
42✔
386
    const hasAuditlogs = isAuditlogEnabled && (!isHosted || isEnterprise || plan === PLANS.professional.id);
42!
387
    const hasDeviceConfig = hasAddons || (isDeviceConfigEnabled && (!isHosted || addons.some(addon => addon.name === 'configure' && Boolean(addon.enabled))));
42!
388
    const hasDeviceConnect =
389
      hasAddons || (isDeviceConnectEnabled && (!isHosted || addons.some(addon => addon.name === 'troubleshoot' && Boolean(addon.enabled))));
42!
390
    const hasMonitor = hasAddons || (isMonitorEnabled && (!isHosted || addons.some(addon => addon.name === 'monitor' && Boolean(addon.enabled))));
42!
391
    return {
42✔
392
      canDelta,
393
      canRetry: canDelta,
394
      canSchedule: canDelta,
395
      hasAuditlogs,
396
      hasDeviceConfig,
397
      hasDeviceConnect,
398
      hasFullFiltering: canDelta,
399
      hasMonitor,
400
      isEnterprise,
401
      plan
402
    };
403
  }
404
);
405

406
export const getAvailableIssueOptionsByType = createSelector(
183✔
407
  [getFeatures, getTenantCapabilities, getIssueCountsByType],
408
  ({ hasReporting }, { hasFullFiltering, hasMonitor }, issueCounts) =>
409
    Object.values(DEVICE_ISSUE_OPTIONS).reduce((accu, { isCategory, key, needsFullFiltering, needsMonitor, needsReporting, title }) => {
12✔
410
      if (isCategory || (needsReporting && !hasReporting) || (needsFullFiltering && !hasFullFiltering) || (needsMonitor && !hasMonitor)) {
72✔
411
        return accu;
66✔
412
      }
413
      accu[key] = { count: issueCounts[key].filtered, key, title };
6✔
414
      return accu;
6✔
415
    }, {})
416
);
417

418
export const getDeviceTypes = createSelector([getAcceptedDevices, getDevicesById], ({ deviceIds = [] }, devicesById) =>
183!
419
  Object.keys(
1✔
420
    deviceIds.slice(0, 200).reduce((accu, item) => {
421
      const { device_type: deviceTypes = [] } = devicesById[item] ? devicesById[item].attributes : {};
2!
422
      accu = deviceTypes.reduce((deviceTypeAccu, deviceType) => {
2✔
423
        if (deviceType.length > 1) {
2!
424
          deviceTypeAccu[deviceType] = deviceTypeAccu[deviceType] ? deviceTypeAccu[deviceType] + 1 : 1;
2!
425
        }
426
        return deviceTypeAccu;
2✔
427
      }, accu);
428
      return accu;
2✔
429
    }, {})
430
  )
431
);
432

433
export const getGroupNames = createSelector([getGroupsById, getUserRoles, (_, options = {}) => options], (groupsById, { uiPermissions }, { staticOnly }) => {
1,300✔
434
  // eslint-disable-next-line no-unused-vars
435
  const { [UNGROUPED_GROUP.id]: ungrouped, ...groups } = groupsById;
1,300✔
436
  if (staticOnly) {
1,300!
437
    return Object.keys(uiPermissions.groups).sort();
×
438
  }
439
  return Object.keys(
1,300✔
440
    Object.entries(groups).reduce((accu, [groupName, group]) => {
441
      if (group.filter || uiPermissions.groups[ALL_DEVICES]) {
2,598!
442
        accu[groupName] = group;
2,598✔
443
      }
444
      return accu;
2,598✔
445
    }, uiPermissions.groups)
446
  ).sort();
447
});
448

449
const getReleaseMappingDefaults = () => ({});
183✔
450
export const getReleasesList = createSelector([getReleasesById, getListedReleases, getReleaseMappingDefaults], listItemMapper);
183✔
451

452
export const getReleaseTagsById = createSelector([getReleaseTags], releaseTags => releaseTags.reduce((accu, key) => ({ ...accu, [key]: key }), {}));
183✔
453
export const getHasReleases = createSelector(
183✔
454
  [getReleaseListState, getReleasesById],
455
  ({ searchTotal, total }, byId) => !!(Object.keys(byId).length || total || searchTotal)
33!
456
);
457

458
export const getSelectedRelease = createSelector([getReleasesById, getSelectedReleaseId], (byId, id) => byId[id] ?? {});
183✔
459

460
const relevantDeploymentStates = [DEPLOYMENT_STATES.pending, DEPLOYMENT_STATES.inprogress, DEPLOYMENT_STATES.finished];
183✔
461
export const DEPLOYMENT_CUTOFF = 3;
183✔
462
export const getRecentDeployments = createSelector([getDeploymentsById, getDeploymentsByStatus], (deploymentsById, deploymentsByStatus) =>
183✔
463
  Object.entries(deploymentsByStatus).reduce(
404✔
464
    (accu, [state, byStatus]) => {
465
      if (!relevantDeploymentStates.includes(state) || !byStatus.deploymentIds.length) {
1,616✔
466
        return accu;
407✔
467
      }
468
      accu[state] = byStatus.deploymentIds
1,209✔
469
        .reduce((accu, id) => {
470
          if (deploymentsById[id]) {
2,343✔
471
            accu.push(deploymentsById[id]);
2,337✔
472
          }
473
          return accu;
2,343✔
474
        }, [])
475
        .slice(0, DEPLOYMENT_CUTOFF);
476
      accu.total += byStatus.total;
1,209✔
477
      return accu;
1,209✔
478
    },
479
    { total: 0 }
480
  )
481
);
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